리액트로 개발을 진행하던 중 redux가 좀 복잡하고 귀찮아서 그냥 context도 써보고 상태관리에 대해 이것 저것 많이 알아보았다.
그러던중 Recoil이라는 오직 리액트를 위한 상태관리 라이브러리가 있는것을 알게 됐다.
Recoil이란?
페이스북에서 개발한 새로운 상태관리 라이브러리. React에서는 자체적으로 상태관리 할 수 있는 Hooks나 ContextAPI를 제공하지만 사용하는데에 있어서 불편한점이 많다.
컴포넌트의 상태는 공통된 상위요소에서 공유될 수 있지만, 많은 컴포넌트가 상위-하위로 연결되어있을 때 이 거대한 트리가 다시 렌더링 되는 현상이 일어난다.
Context는 단일 값만 저장할 수 있고, 여러 값들의 집합을 담을 수 없다.
이러한 특성들이 트리의 최상단으로 부터 최하단까지의 코드의 분할을 어렵게 만든다.
간단한 리액트 프로젝트라면 Context만을 이용해서 충분히 해결 가능하다. 하지만 컴포넌트 구조가 복잡해질 수록 위 같은 단점들 때문에 여러 상태관리 라이브러리(Redux, Mobx, Recoil 등)들이 생기기 시작했다.
Recoil의 장점
페이스북 리액트 코리아 그룹의 한 포스트의 내용이다.
- 공유상태(shared state)에도 React의 로컬 상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다. (필요한 경우 reducers 등으로 캡슐화 할 수 있다.
- 동시성 모드(Concurrent Mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.
- 상태 정의는 증분 및 분산되므로 코드 분할이 가능하다.
- 상태를 사용하는 컴포넌트를 수정하지 않고 파생된 데이터는 동기식과 비동기식간에 이동할 수 있다.
- 역호환성 방식으로 전체 애플리케이션 상태를 유지하는 것은 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.
Recoil의 주요 개념
Recoil을 사용하면 atoms(공유 상태)에서 selectors(순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow-graph를 만들 수 있다.
atoms는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
Atoms
atom은 상태의 단위로, 값이 업데이트되면 값을 구독한 컴포넌트는 다시 렌더링 된다.
atoms는 atom()함수를 사용해 생성할 수 있다.
const countState = atom({
key: 'countState', // 전역적으로 고유한 값이어야 함
default: 0, // 관리할 상태 값의 초기값
});
컴포넌트에서 atom을 읽고 쓰는 방법은 useRecoilState, useRecoilValue, useSetRecoilValue 라는 훅을 사용해야 한다.
useRecoilState는 useState와 비슷한 형태로 사용한다.
const Couter = () => {
const [count, setCount] = useRecoilState(countState);
return <button onClick={() => setCount((c) => (c + 1))>+</button>
}
useRecoilValue는 상태 값을 불러올 때, useSetRecoilValue는 상태 값을 지정할 때 따로따로 사용할 수 있다.
...
const count = useRecoilValue(CountState);
const setCount = useSetRecoilValue(CountState);
...
Selectors
selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수 이다. 상위의 atoms 또는 selectors가 업데이트 되면 하위의 selector함수도 다시 실행 된다.
Selectors는 상태를 기반으로 하는 파생 데이터(매번 계산되는 대신 파생된데이터 저장)를 계산하는데 사용된다.
최소한의 상태 집합만 atoms에 저장하고 다른 모든 파생되는 데이터는 selectors를 통해 계산함으로써 쓸모없는 상태의 보존을 방지해 주는 역할을 한다.
const counterLabelState = selector({
key: 'counterLabelState',
get: ({get}) => {
const count = get(countState);
return `count: ${count}`;
}
});
get 속성은 계산될 함수이다. get인자를 통해 atoms와 다른 selectors에 접근할 수 있는데, 참조했던 다른 atoms나 selectors가 업데이트 되면서 이 함수도 다시 실행된다.
selectors는 useRecoilValue를 사용해 읽을 수 있다.
selector로 구성된 counterLabelState는 writable하지 않기 때문에(Readable) useRecoilState, useSetRecoilValue를 사용하지 않는다.