Redux
를 사용해보고, 또 Redux의 boilerplate를 개선한 Redux-toolkit
을 사용했었다. 사이드 프로젝트와 과제 테스트를 수행하면서 페이스북에서 만든 React 친화적인 상태관리 라이브러리 Recoil
을 사용해보기로 했다. (store와 같은 외부 요인이 아닌 React 내부의 상태를 활용하고 Context API를 기반으로 구현되어있기 때문에 다른 상태 관리 라이브러리에 비해 상대적으로 리액트에 친화적인 라이브러리라고 한다.)Context API
도 고려해봤다.Context API: Context API는 props를 사용하지 않아도 특정 값이 필요한 컴포넌트끼리 쉽게 값을 공유할 수 있게 해준다. 다만, 공유하고자하는 state가 있는 컴포넌트를 Provider로 감싸게 되는데, 상태값이 변경되면 Provider로 감싼 모든 자식 컴포넌트들이 re-rendering되므로 전역 상태 관리를 위한 도구가 아닌, 데이터를 쉽게 전달하고 공유하기 위한 목적으로 사용하는 것이 적합하다. 따라서 테마 데이터, 언어 혹은 지역 데이터 등과 같이 변경이 자주 일어나지 않는 데이터를 다룰때 용이하다.
npm install recoil
yarn add recoil
recoil 상태를 사용하는 컴포넌트는 부모 트리에 RecoilRoot
가 필요하다. 전역적인 상태관리를 위해 루트 컴포넌트에 RecoilRoot를 넣어준다. Redux를 사용할 때 루트에 Provider 태그로 감싸주는 것처럼 Recoil은 RecoilRoot를 감싸주면 된다.
// App.js
import { RecoilRoot } from "recoil";
import Home from "./components/Home";
function App() {
return (
<RecoilRoot>
<Home />
</RecoilRoot>
);
}
export default App;
useRecoilState
훅을 사용한다. atom의 값을 변경하면, 그것을 구독하고 있는 컴포넌트들이 모두 다시 렌더링된다. atom을 생성하기 위해 고유한 key
값과 default
값을 설정해야 한다.// atoms.js
import { atom } from 'recoil';
// 예시: 사용자 이름을 담는 atom
export const userNameState = atom({
key: 'userNameState',
default: '',
});
get
함수만 사용하면 읽기 전용이며, get 함수를 통해 atom의 값을 가져와서 변형한 값을 반환(RecoilValueReadOnly
객체 반환)한다. atom을 쓰기(업데이트)할 수 있는 set
함수를 옵션으로 받을 수 있다.// selectors.js
import { selector } from 'recoil';
import { atom } from 'recoil';
// 예시: 대문자로 변환된 사용자 이름을 반환하는 selector
export const upperCaseUserNameSelector = selector({
key: 'upperCaseUserNameSelector',
get: ({ get }) => {
const userName = get(userNameState);
return userName.toUpperCase();
},
});
// CurrentUserInfo.js
const currentUserNameQuery = selector({
key: 'CurrentUserName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});
function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}
import { RecoilRoot } from "recoil";
import Home from "./components/Home";
function App() {
return (
<RecoilRoot>
<React.Suspense fallback={<div>Loading...</div>}>
<Home />
</React.Suspense>
</RecoilRoot>
);
}
export default App;
useRecoilState
: atom의 값을 구독하여 업데이트할 수 있는 hook으로, 첫 요소가 상태의 값이며, 두번째 요소가 호출되었을 때 주어진 값을 업데이트하는 setter 함수를 반환한다. 리액트의 useState
와 동일한 방식으로 사용할 수 있다.useRecoilValue
: setter 함수 없이 atom의 값을 반환만 한다.useSetRecoilState
: setter 함수만 반환한다.useResetRecoilState()
, useRecoilStateLoadable()
등 더 많은 hooks들이 있다.// atoms.js
import { atom } from 'recoil';
export const countState = atom({
key: 'countState',
default: 0,
});
// Component.js
import { useRecoilState, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil';
import { countState } from './atoms';
const Component = () => {
const [count, setCount] = useRecoilState(countState); // 상태를 읽고 쓰는 훅
const currentCount = useRecoilValue(countState); // 상태를 읽는 훅
const setCountWithUpdater = useSetRecoilState(countState); // 상태를 설정하는 훅
const resetCount = useResetRecoilState(countState); // 상태를 초기화하는 훅
// 현재 값에 1을 더하여 recoil 상태 업데이트
const handleIncrement = () => setCount(count + 1);
// 현재 값에 1을 빼서 recoil 상태 업데이트
const handleDecrement = () => setCount(count - 1);
// recoil 상태를 초기화
const handleReset = () => resetCount();
const handleAsyncUpdate = () => {
// 비동기로 recoil 상태 업데이트
setTimeout(() => {
setCountWithUpdater((prevCount) => prevCount + 2);
}, 1000);
};
return (
<div>
<p>Count: {count}</p>
<p>Current Count (useRecoilValue): {currentCount}</p>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
<button onClick={handleReset}>Reset</button>
<button onClick={handleAsyncUpdate}>Async Update</button>
</div>
);
};
export default Component;
Reference.