Recoil은 react를 위한 상태관리 라이브러리(state management library)이다.
기존의 react 상태관리 기능은 다음과 같은 한계를 가지고 있었고, 이것을 개선하고자 Recoil이 개발되었다.
- 컴포넌트의 상태는 공통된 상위요소까지 끌어올림으로써 공유될 수 있지만, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
- Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.
- 이 두가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 잎(state가 사용되는 곳)까지의 코드 분할을 어렵게한다.
Recoil이 이 문제점들을 어떻게 해결하는지 알아보자.
먼저 react 내부 상태 관리 기능을 사용한다면 아래와 같은 거대한 트리가 형성되어 앞서 언급한 문제들을 야기한다.
Recoil은 어떤 컴포넌트에서나 접근 가능한 state(global state)인 atom을 도입해서 이 문제들을 해결한다.
Recoil은 아래 명령어로 설치할 수 있다.
npm install recoil
또는
yarn add recoil
컴포넌트 내에서 recoil state를 사용하기 위해서는 부모 트리 어딘가에 RecoilRoot 컴포넌트를 넣어줘야 한다.
import React from 'react';
import ReactDOM from "react-dom/client";
import App from "./App";
import { RecoilRoot } from 'recoil';
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<RecoilRoot>
<App />
</RecoilRoot>
);
Atom은 상태(state)의 일부를 나타낸다. Atoms는 어떤 컴포넌트에서나 읽고 쓸 수 있다. Atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독(subscribe)한다. 그래서 atom에 어떤 변화가 있으면 그 atom을 구독하는 모든 컴포넌트들이 재렌더링 되는 결과가 발생할 것이다.
import { atom } from 'recoil';
export enum Categories {
TO_DO = "TO_DO",
DOING = "DOING",
DONE = "DONE",
}
export const categoryState = atom<Categories>({
key: "category", // unique ID (with respect to other atoms/selectors)
default: Categories.TO_DO, // default value (aka initial value)
});
컴포넌트가 atom을 읽고 쓰게 하기 위해서는 useRecoilState()를 아래와 같이 사용하면 된다.
import { useRecoilState } from 'recoil';
export function App() {
return (
<>
<ToDoList />
<Search />
</>
);
};
function ToDoList() {
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event) => {
setCategory(event.currentTarget.value);
};
return (
<select value={category} onInput={onInput}>
<option value={Categories.TO_DO}>To Do</option>
<option value={Categories.Doing}>Doing</option>
<option value={Categories.Done}>Done</option>
</select>
)
};
Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태는 상태의 변화다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.
import { selector } from 'recoil';
export const toDoSelector = selector({
key: "toDoSelector",
get: ({ get }) => {
const toDos = get(toDoState);
const category = get(categoryState);
return toDos.filter((toDo) => toDo.category === category);
},
});
useRecoilValue() 훅을 사용해서 selector의 값을 읽을 수 있다.
import { useRecoilState, useRecoilValue } from 'recoil';
function ToDoList() {
const toDos = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event) => {
setCategory(event.currentTarget.value);
};
return (
<>
<select value={category} onInput={onInput}>
<option value={Categories.TO_DO}>To Do</option>
<option value={Categories.Doing}>Doing</option>
<option value={Categories.Done}>Done</option>
</select>
<ul>
{toDos.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</ul>
</>
)
};
참고자료