🗣️ 상태관리 라이브러리인 Recoil를 알아보고 사용해봅시다!
프로젝트로 이커머스 사이트를 만드는 중에 문제가 발생했다. 상품을 장바구니에 담기 버튼을 누르면 장바구니에 담아지도록 구현했다!
장바구니 페이지에 들어가는 건 물론이고, 아래 사진과 같이 페이지 헤더에 있는 장바구니 이모티콘에 장바구니에 상품이 몇개 들어있는지 표현하고 싶었다.
그래서 useState와 로컬스토리지를 사용해 클릭할때마다 상품의 아이디를 담고, 수량을 담아서 헤더에서 getitem를 이용해 총 상품의 수를 불러오기는 성공했지만 장바구니에 담을때마다 렌더링이 안 되고, 꼭 새로고침을 해줘야 했다.
그런 점이 UI적으로는 불편하다고 느껴져 해결방법을 찾아야 했다.
react에서 사용할 수 있는 상태관리 라이브러리가 있다는 것을 알았다. 이것을 이용하면 전역적으로 변수를 사용할 수 있다는 것이다. 꽤 다양한 종류가 많았지만, 페이스북에서 만든 react 전용 라이브러이인 recoil를 사용해보기로 했다.
예전에 강의를 들었을때도 상태를 관리할 수 있다는 걸 알긴 했지만, 도통 들어도 무슨 소리인지 이해를 못해서 props만 주구장창 써서 코드가 길어지곤 했다. 그러다보니 내가 작성한 코드인데도 이해하기 어려워지기 시작했다.
그래서 드는 생각이... 이대론 안 되겠는데? 싶었다. 게다가 장바구니 기능도 구현하고 있던터라 한 번 사용해보면 좋을 것 같았다.
✏️ recoil를 쓰게 된 이유는 React에 최적화되어 있어서 React 문법과 유사하기 때문이다. react를 사용하면 꼭 사용할 useState과 같이 [number, setNumber]..으로 사용할 수 있다는 것이다. react를 조금이라도 한다면 쉽게 사용할 수 있을 것이다.
Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있다. Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다. Selectors는 atoms 상태 값을 동기 또는 비동기 방식을 통해 변환한다.
npm
npm install recoil
yarn
yarn add recoil
이때 Recoil은 ES5와 사용하는 것은 지원하지 않는다. ES6 기능을 natively하게 제공하지 않는 브라우저를 지원해야 하는 경우 Babel로 코드를 컴파일하고 preset @babel/preset-env을 이용하여 이를 수행할 수는 있지만 문제가 발생할 수도 있다.
<RecoilRoot>
선언해주기// App.js
import React from 'react';
import { RecoilRoot } from 'recoil';
const App = () => {
return (
<RecoilRoot>
<Common />
</RecoilRoot>
);
}
본인이 사용할 곳을 <RecoilRoot>
로 감싸준다. 보통 전역에서 사용하기 때문에 App.js에서 전체를 감싸주면 이제 아래에 있는 곳 어디에서든 사용가능하다. 타입스크립트를 사용 중이라면 App.tsx에 적용시키면 된다!
먼저 Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트되면 각각 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
쉽게 이야기 하자면,
const [count, setCount] = useState(0)
에서의 count가 atom이라고 생각하면 된다!
따라서 나는 atom를 보기 쉽게 관리하기 위해서 따로 폴더를 만들었다. recoil이라는 폴더를 만들고 그 안에 fontSizeState.js를 만들어 선언했다.
//NumberAtom.js
import { atom } from "recoil"
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
✨ 여기서 key
는 전역적으로 고유한 이름이어야 한다!
그리고 default
는
const [count, setCount] = useState(0)
여기에서 괄호 안에 적어주는 숫자 0처럼 사용할 기본값
을 말한다. 그래서 상황에 따라 본인이 사용할 기본값을 적어주면 됩니다.
먼저 사용할 페이지로 이동한 다음, atom를 읽어 와줍니다.
atom를 읽으려면 useRecoilState를 사용해 해당하는 것을 읽어올 수 있습니다.
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
여기서 괄호 안에 recoil 폴더 안에 만든 페이지에서 atom을 불러와주면, fontSize에 기본값이 들어가고, setFontSize으로 연산할 수 있다.
아니면,const fontSize = useRecoilState(fontSizeState);
이렇게만 해도 지정된 만들어진 값을 가져올 수 있다!
useState와의 차이점은 reocil는 전역적으로 컴포넌트를 불러올 수 있다는 것이다.
따라서 버튼을 누르면 전역적으로 숫자가 증가하는 것을 볼 수 있다.
Selector
는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수다.
이전에 설정해두었던 atoms 또는 selectors가 업데이트되면 생성하는 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링 된다.
쉽게 이야기 하면 만들었던 atom의 값을 가지고 새로운 값을 만들어낼 수 있다는 것이다.
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
여기서 get
는 계산될 함수이다. get를 통해 원하는 값을 조작해주고 return 해준다.
✅ 따라서 atom으로 선언한 fontSizeState의 값이 만약 24라면 그것을 가져오고, 'px'라는 문자열을 선언한 후에 해당 selector인 fontSizeLabelState가 실행되면 24px를 리턴해주는 동작이다.
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
<div>Current font size: ${fontSizeLabel}</div>
<button onClick={setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
}
따라서 이렇게 사용할 수 있다.
useRecoilValue
를 사용해서 선언해두었던 selector인 fontSizeLabelState를 불러와줍니다. 그러면 fontSizeLabel의 값은 위에 selector에서 get 메소드를 구성했던 것처럼 폰트 크기를 늘리는 버튼을 누를때마다 새로 렌더링되어서 24px이라는 값이 담아지면서 같이 렌더링 됩니다!
//CartitemAtom.js
import { atom } from "recoil"
export const CartitemAtom = atom({
key: 'CartitemAtom',
default: []
})
장바구니를 구성할 atom를 만들어줍니다. 초기값은 배열로 만듭니다.
//productpage.js
const [cartItem, setCartItem] = useRecoilState(CartitemAtom)
const isAlreadyInCart = // 같은 상품의 id가 있는지 검증하기
const AddToCart = () => {
if(isAlreadyInCart) {
alert('이미 장바구니에 있습니다!')
} else {
setCartItem((prev)=>[...prev, {클릭한 상품의 data}])
alert('장바구니에 담았습니다!')
}
}
장바구니 담기 버튼이 있는 상품 페이지에 useRecoilState를 사용하여 atom를 불러옵니다.
setCartItem를 사용해 이전에 있던 상품들과 같이 추가되도록 구현합니다. AddToCart는 버튼의 onClick에 넣어줍니다.
//pageheader.js
const cartItem = useRecoilState(CartitemAtom) // 장바구니 리스트
console.log(cartItem.length) // 장바구니 길이. 즉, 장바구니에 담긴 상품 개수
atom를 사용할 페이지에 불러와주고, length로 장바구니 길이를 알 수 있습니다.
길이를 여기에 넣어주면 장바구니에 물건이 담기거나 삭제될때마다 변하는 것을 확인할 수 있습니다!! 끝!
다음번엔 다른 상태관리 라이브러리도 사용해야겠다!!!🔥🔥🔥🔥