Recoil이란?
리액트를 공부하면 꼭 짚고 넘어가야 하는 부분이 바로 전역 상태 관리입니다. 전역 상태 관리를 쌩(?)으로 하려면 지옥 같은 Props Drilling을 맛보게 될 겁니다. Props Drilling을 더한 상태 관리가 생긴다면, 코드를 읽을 때 props를 추적하기 굉장히 힘들어지고, 추적하다가 개발 시간을 다 잡아먹고 말 것입니다. 이러한 이유로 Redux, MobX, Context API, 오늘 알아보고자 하는 Recoil까지 다양한 상태 관리 라이브러리들을 사용합니다.
제가 가장 처음 접한 상태 관리 라이브러리는 바로 Redux였습니다. DND 포스팅 참고
최근 1년간의 npm trends를 살펴보면 Redux가 압도적으로 많은 것을 알 수 있습니다. Redux는 7년이라는 역사를 가진 라이브러리이고, 보통의 프론트 개발자들이 Redux는 한번씩 다 접해 봤고 사용하다 보니, Recoil과 Mobx에 비해서 React-Redux 라이브러리가 압도적인 다운로드 수를 자랑할 수 있는 것 같습니다.
하지만, Recoil은 Redux를 제치고 사용 할 만큼 괜찮은 라이브러리라고 생각합니다.
Why Recoil?
1. Redux와 Mobx의 API는 절대 단순하지 않습니다.
Redux를 처음 배우면 많은 난관에 봉착합니다. 간단하게 Redux를 사용하고자 해도 Action, Store, Reducer, dispatcher 같은 것들을 구현해야 하고, 더 들어가 redux-promise-middleware를 활용하기 위해서는 Redux-thunk나 Redux-saga 같은 라이브러리를 추가로 생각해야 하기 때문입니다. 결론은, Recoil는 자신을 전역 상태 관리 라이브러리이지만, React 답게 유지한다면서 굉장히 간단한 구조로 코드를 작성할 수 있다고 말합니다.
2. Redux의 비동기 처리
리덕스를 사용 하면서 비동기 작업 (네트워크 요청)을 다룰 때는 미들웨어가 있어야 손쉽게 처리할 수 있습니다. 또는, Redux-thunk나 Redux-saga의 promise-middleware를 사용하여 비동기 방식을 처리합니다. 하지만, Recoil의 Selector를 이용하면 비동기 Action에 대한 처리가 가능합니다.
3. 태생이 React입니다.
Redux나 Mobx는 React에 종속적인 라이브러리가 아니지만, Recoil은 태생이 React이고, 동시성 모드 같은 호환도 같습니다.
Recoil 시작하기
간단하게 공식문서에 나와있는 Recoil을 맛 볼 수 있는 코드를 작성해봅시다. 먼저 CRA를 통해 리액트 프로젝트를 생성하고 Recoil을 설치합니다.
npm install recoil
yarn add recoil
npm 또는 yarn을 통해 recoil을 설치합니다.
RecoilRoot
어느 상태 라이브러리와 똑같이 Recoil 상태를 사용하기 위해서, recoil 상태를 사용하는 컴포넌트 부모에 <RecoilRoot>를 감싸주면 됩니다. CRA에 있던 App.js 부분을 전부 날리고 다음 코드를 작성해 주었습니다.
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
Atom
Recoil의 중요 개념인 Atom입니다. Recoil에서 Atom은 간단하게 상태를 정의하는 방법입니다. 상태를 정의할 땐 고유한 key 값을 설정하고, 기본 값을 담으면 됩니다. 이렇게 정의한 atom에 useRecoilState, useRecoilValue 같은 recoil에서 제공하는 Hook을 사용할 수 있습니다.
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});
Atom은 어떤 컴포넌트에서 읽고 쓸 수 있다는 특징이 있고, atom 값을 읽는 컴포넌트들은 atom을 감시하고 있다가 변화가 있으면 atom을 사용하고 있는 컴포넌트들이 자동으로 재 랜더링 하게 됩니다.
Atom을 사용하는 방법
function CharacterCounter() {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
}
function TextInput() {
const [text, setText] = useRecoilState(textState);
const onChange = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
문자: {text}
</div>
);
}
React Function useState훅처럼 생긴 useRecoilState를 통해서 다음 코드를 사용할 수 있습니다. Redux의 dispatcher, reducer 같은 건 필요 없이 간단하고 가장 React 다운 상태 관리 툴이라고 불릴 만한 Hook을 제공해 주고 있습니다.
Selector
또 다른 중요한 개념인 Selector입니다. 공식 문서에서는 파생된 상태(derived state)라고 하는데. 말이 어려워서 쉽게 말하자면, atom을 원하는 대로 변형하여 값을 받는다고 생각할 수 있습니다. 그리고 Selector는 Setter 또는 Updator는 사용할 수 없고 오직 읽기 전용으로만 사용이 가능합니다.
const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>문자 길이: {count}</>;
}
다음 코드 같이 해당 text 값을 새로 등록하거나 수정할 순 없으며 오직 다음 selector를 통해 변형된 값만 받을 수 있습니다. 더 파고들면 양방향 Selector와 비동기 Selector 등 다양한 개념들이 존재합니다. Recoil의 심화된 내용으로 포스팅해보겠습니다.
결과