1인 프로젝트 개발을 하던 중 전역 상태 관리가 필요해진 시점에서 누군가 나에게 말했다 useRecoil을 쓴다면 편하게 전역관리를 할 수 있으니 시도해보지 않겠냐고
'상태 관리'에 대한 정의
-
애플리케이션의 동작 방식을 설명하는 모든 데이터
-
David Khourshid - 상태관리는 시간이 지남에 따라 상태가 변경되는 방식이다.
-
상태 '관리'가 되려면 기본적으로 3가지 기능을 해야한다.
- 최초 값(initial value)를 저장
- 현재 값(current value)를 읽기
- 값을 업데이트
그렇다면 Recoil은 왜필요한가?
- React의 내장 상태 관리 '기능'의 한계점 극복
- 그 중에 최대한 React스러운 API 유지
- 어떠한 특별한 runningCommand없이 간단하게 접근이 가능하다.
- 사용하기 위한 부속 라이브러리 최소화
React 상태 관리 로직의 한계점
- 컴포넌트의 상태는 공통되는 부모 컴포넌트까지 올라가야하는데, 심할 경우 애플리케이션 상단까지 올라가야하는 문제가 발생할 수 있음
- Context API는 확정되지 않은 수의 값을 저장하는데 적합하지 않으며, 최적화 관점의 한계점이 명확함
- Context API는 각 상태마다 Context Provider가 필요한데, 상태가 1000개라면 1000개의 Provider를 선언해야한다.
- 상태가 변경될 때마다 캐싱 없이 매번 re-rendering 한다.
- Redux는 별도의 라이브러리가 필요하고 초기 구축, 유지 보수 과정에서 boilerplate 코드를 많이 작성해야 한다.
- React concurrent renderer를 지원하지 않는다.
Recoil의 접근 방법
- Recoil은 React Tree에 직교되는 형태로 존재하는 방향 그래프(Directed Graph)로 구성되어 있다.
- 상태의 변경은 이 그래프를 따라 React Component로 흘러 들어간다.
- 다양한 장점들이 있지만, 가장 큰 장점으로 Component쪽의 로직을 건드리지 않고 상태 데이터를 단독으로 변경할 수 있다는 큰 장점이 있다.

- 다양한 장점들이 있지만, 가장 큰 장점으로 Component쪽의 로직을 건드리지 않고 상태 데이터를 단독으로 변경할 수 있다는 큰 장점이 있다.
Recoil의 철학
- boilerplate가 적은 API면서 React의 로컬 상태(useState, useReducer) 유사한 간단한 인터페이스
- Concurrent Mode와 호환
- 코드 상호간의 낮은 결합도를 통해 Code splitting 용이성 확보
- 파생 데이터를 사용함으로써 데이터를 사용하는 컴포넌트에서 임의로 데이터를 바꾸는 로직을 가져가지 않아도 된다.
- 가져와서 useEffect로 바꿔주기를 하지 않고, 로직 자체를 Recoil atom에 귀속시킬 수 있다.
// 기존 useEffect로 업데이트 해줘야하는 점
useEffect(() => {
(async () => {
await updateState();
await updateAnotherState();
}) ();
}, []);
// 업데이트 필요없이 useState와 유사한 업데이트 방식
const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});
const tempCelsius = selector({
key: 'tempCelsius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32),
});
function TempCelsius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);
Core Concept
Atom
- 데이터를 보관하는 기본 단위
- Redux로 따지자면 store와 유사한 개념으로 상태의 단위를 뜻한다.
- atom이 업데이트 된다면 해당 atom을 구독하고 있던 모든 Component의 state가 업데이트 된다.
- 유니크한 id인 key값으로 구분되는 각 atom은 여러 컴포넌트에서 해당 atom을 구독하고 있다면 그 컴포넌트도 똑같은 상태를 유지한다.
state.js
export const cookieState = atom({
key: 'cookieState',
default: []
});
Cookie.js
import React from 'react'
import { cookieState } from '../../state';
import { useRecoilState } from 'recoil';
const Cookie = () => {
const [cookies, setCookies] = useRecoilState(cookieState);
return(
<div>
{cookie.map(cookie => (
<Card
cookies={cookie}
key={cookie.id}
idx={cookie.id}
/>
))}
</div>
);
}
export default Cookie;
Selector
- selector는 atom을 전/후 처리하여 새로운 값을 리턴하거나 기존 atom의 값을 수정할 때 사용한다.
- atom의 값이 최신화 되면 selector의 값 또한 최신화되어 편하게 사용할 수 있다.
- selector를 사용하면 atom을 직접 변경하지 않기 때문에 불변성 유지가 가능하다.
- selector는 순수함수로 같은 입력이 들어오면 해당 입력에 대한 출력은 항상 같은 함수라는 것을 의미한다.
- selector는 read-only한 RecoilValueReadOnly 객체로써 return값 만을 가질 수 있고 값을 set 할 수 없는 특징이 있다.
export const sampleState = atom({
key: "sampleState",
default: 0,
});
export const sampleSelector = selector({
key: "sampleSelector",
get: ({ get }) => get(sampleState) * 2,
set: ({ set }, newValue) => set(sampleState, newValue / 2),
});
마치며
useRecoil에 대해서 알아보니 편리한 사용성과 간단한 로직이 마음에 들었다. 간결한 코드와 컴포넌트의 로직을 건드리지 않고 상태관리를 할 수 있단 점이 마음에 들었고 아직 기초적인 것만 활용해 잘은 모르지만 이를 활용해 프로젝트를 구현해보려고 한다.