Recoil 과 상태 관리 라이브러리
Recoil은 Facebook에서 만든 상태 관리 라이브러리다.
이거 하나 만으로 믿음이 생기겠지만, React 만든 곳이 아닌 다른 팀에서 만든 라이브러리고 최근 IT시장이 안좋아지자 Facebook도 구조조정에 영향이 갔는지 Recoil은 없데이트하고 있다.
redux, zotai, zustand 등 다른 상태관리 라이브러리를 각자 장단점이 있으나 이 글의 주제인 recoil은 이 정도 장단점을 가지고 있다는 것만 알아보면
- React만 위한 라이브러리 (친화적)
- React Suspense 쉽게 사용
- Redux 다음으로 커뮤니티(정보)가 큼
단점으로는 프로덕션 환경에서 사용할 때 실험 버전이라 신중해야 할 필요가 있다. (이게 제일 큼)
버그 수정 같은 업데이트 밖에 안하고 2020년에 만들어진 라이브러리가 아직도 메이저 버전(1.0.0 부터 시작하는)을 내놓지 않고 있다.. (진짜 개발자 짤린 듯)
이용자는 redux, zustand가 많은 편인데 이 라이브러리들은 react에서만 국한된게 아니기 때문에 이용자가 아무래도 많을 수 밖에 없다.
그렇다 하더라도 아직까진 redux와 zustand가 엄청 대세인듯!
Recoil hooks
자주 쓰는 훅들을 정리해보자.
useRecoilValue
const todos = useRecoilValue(atomTodos)
atom에 저장된 값을 반환한다. `get`
값이 바뀔 경우 해당 컴포넌트는 리렌더링의 대상이 된다.
useSetRecoilState
const setTodos = useSetRecoilState(atomTodos)
const handleAddTodos = () => {
const todo = { ... }
setTodos(todos => [...todos, todo] )
}
atom의 상태를 바꾸는 setter 함수를 반환한다. `set`
상태가 바뀌어도 해당 컴포넌트는 구독(value)되지 않았기 때문에 리렌더링의 영향이 없다.
useRecoilState
const [todos, setTodos] = useRecoilState(atomTodos)
위의 두 훅을 합친 것이다. `get`, `set`
selector
const filteredTodoListState = selector({
key: 'filteredTodoListState',
get: ({get}) => {
// todoListFilter: "Show Completed" | "Show Uncompleted"
const filter = get(todoListFilterState);
const list = get(todoListState);
switch (filter) {
case 'Show Completed':
return list.filter((item) => item.isComplete);
case 'Show Uncompleted':
return list.filter((item) => !item.isComplete);
default:
return list;
}
},
});
새로운 atom을 만들 필요 없이 다른 상태로 바꿔서 반환한다. 비즈니스 로직을 숨긴채로 다양한 값을 반환할 수 있다.
`useRecoilState()`에서만 동작 가능
atomFamily
const atomFamilyTodos = atomFamily({
key: 'atomFamilyTodos',
default: [0, 0],
});
// something component
const TodoButton = ({id}) => {
const todo = useRecoilValue(atomFamilyTodos(id))
return ...
}
`atom()` 과 거의 같지만 파라미터를 받아 분리된 atom을 한 번에 관리할 수 있다.
구독도 개별 관리 되어 해당 요소만 그 atom만 구독하는 컴포넌트만 리렌더링 된다.
atom Effects
const atomTodoIdsEffect =
(key: string) =>
({ setSelf, onSet }: any) => {
const savedValue = localStorage.getItem(key)
if (savedValue != null) {
// setSelf: 초기값 지정
setSelf(JSON.parse(savedValue))
}
// onSet: 값이 바뀔 때마다 실행
onSet((newValue: any, oldValue: any, isReset: boolean) => {
isReset
? localStorage.removeItem(key)
: localStorage.setItem(key, JSON.stringify(newValue))
})
}
export const atomTodoIds = atom<number[]>({
key: "atomTodoIds",
default: [],
effects: [atomTodoIdsEffect("todo-ids")],
})
effects(부수효과)를 관리하고 atom을 초기화나 동기화하기 위한 것
- 상태 동기화, 히스토리 관리, loggin(로깅) 등에 유용하다.
React의 `useEffect()` 와 비슷하지만 대체 못하는 경우도 있다. (SSR, 동적으로 생성된 atom 같이 react 외부에서 작용하는 것들)
그 외
- `useRecoilCallback()` - `useCallback()`과 비슷한데, Recoil의 상태에서 동작한다.
- `useResetRecoilState()` - 상태를 default 값으로 리셋한다. 컴포넌트를 구독하지 않고도 리셋하게 해줌.
useRecoilTransaction_UNSTABLE
// document example
const dispatch = useRecoilTransaction_UNSTABLE(({get, set}) => action => {
switch(action.type) {
case 'goForward':
const heading = get(headingState);
set(positionState, position => {
x: position.x + cos(heading) * action.distance,
y: position.y + sin(heading) * action.distance,
});
break;
case 'turn':
set(headingState, action.heading);
break;
}
});
다수의 atoms에 대해서 동작을 수행하기 위한 `reducer 패턴`이 가능하다.
setter 함수에 비즈니스 로직이 많으면 유지보수가 어렵다 느껴졌다.
hook을 활용하는 방법도 있으나 애초에 store와 가까운 단에서 해결하는 것이 상태 관리와 더 가깝게 느껴져 이 함수를 찾게 되었다.
함수명에 UNSTABLE 딱지가 붙은 이유가 있었는데 찾아보니
- Atom만 지원하며, Selector는 지원하지 않는다.
- Atom은 동기적으로 읽을 수 있어야하며, 동기적인 트래잭션만 허용한다.
- 사이드이펙트가 불가하다.
와 같은 이유로 폐기처분했다.
recoil reducer pattern 에 대해 정리하면서 느낀점
마무리하면서..
Recoil은 상태 관리를 간단하게 할 수 있고 react hook과 다를 바 없어 편했다.
또한, 공식 문서 또한 한글 번역이 잘 되어있고 예제도 어느정도 있어서 러닝 커브는 낮았다.
그리고 effect 를 통해 사이드 이펙트를 쉽게 관리하는 점이 매력적으로 다가왔다!
하지만, reducer 함수를 쉽게 지원해주지 않는 점, 메이저 패치를 아직까지도 미루고 있다는 것이 아쉬웠다 😂
참고
'개발 (Dev) > Library' 카테고리의 다른 글
[hello-pangea/dnd] draggable id 관련 에러 해결 방법 (0) | 2024.02.28 |
---|---|
[Chrome Extension] 압축 파일 업로드 시 에러 (0) | 2024.01.31 |
NPM Package 올리기 & Github Action 이용해서 배포 자동화 (1) | 2024.01.24 |