[React] Redux 대신에 Recoil 써보기

12866 단어 ReactRecoilreduxReact

처음 개발을 시작할 때는 전역 상태 관리가 필요한지도 몰랐지만 다양한 프로젝트를 하면서 필요성을 깨닫고 리액트에 포함된 Context API, 가장 많이 쓰는 Redux를 거쳐서 이번에는 Recoil을 써보기로 했다. React를 만든 Meta(당시 Facebook)이 개발한 라이브러리이기도 하고 주위에서 State Hook과 사용법이 거의 같다고 해서 한번 시도해봤다.

기본 사용법

가장 기초적인 구성요소로 atomselector가 있다.

atom

atom은 흔히 생각하는 state와 비슷하다.

const textState = atom({
  key: 'textState', // 고유한 식별자
  default: '', // 기본값 (혹은 초기값)
});

위와 같이 선언하면 default에 지정한 값으로 초기화된다. key는 반드시 고유해야 하며, 만약 같은 key를 가진 atom이나 selector가 있다면 오류가 발생한다.

사용할 때는 아래와 같이 사용할 수 있다.

const [text, setText] = useRecoilState(textState);

State Hook과 사용법이 굉장히 비슷하다는 것을 알 수 있다. 단지 생성(atom)과 사용(useRecoilState)이 분리되어 있다는 차이가 있다.

selector

selector는 이미 존재하는 atom들이나 다른 값들을 이용하여 파생된 값을 생성할 수 있다.

const charCountState = selector({
  key: 'charCountState', // 고유한 식별자
  get: ({get}) => {
    const text = get(textState);

    return text.length;
  },
});

atom과 비슷하지만 default 대신에 get을 정의한다. get은 selector가 호출됐을 때 어떤 값을 돌려줄지 정의하는 함수를 인자로 받는다. 이 함수는 인자로 주어진 get 함수를 이용해서 다른 atom의 값을 가져오거나 다른 값을 이용하여 새로운 값을 생성하고 이를 반환한다.

이쯤에서 selector는 읽기만 가능한가 궁금할 수 있겠지만 사실 가능하다. selector를 정의할 때 set을 같이 정의하면 된다. get은 필수지만 set은 아니다.

const proxySelector = selector({
  key: 'ProxySelector',
  get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
  set: ({set}, newValue) => set(myAtom, newValue),
});

atom에서의 set 함수는 기존의 state를 인자로 받은 값으로 완전히 교체하지만, selector의 set 함수는 인자를 받아서 그 인자와 기존의 값들을 사용하여 새로운 상태를 설정한다. 위의 예시에서는 newValue를 바로 myAtom에 설정했지만 다른 atom의 값을 이용하여 파생된 값을 설정하는 것도 가능하다.

Hooks

이제 atom과 selector를 사용할 때 필요한 Hooks에 대해 알아보자.

useRecoilValue

먼저 값만을 불러오는 useRecoilValue에 대해서 알아보자.

const count = useRecoilValue(charCountState);
const proxy = useRecoilValue(proxySelector);

읽기만 가능한 값을 반환하며 이 값을 사용한 컴포넌트나 함수는 이 값을 구독하게 된다. 값이 변하면 구독하고 있는 컴포넌트가 업데이트 된다.

useRecoilState

useRecoilState는 React의 State Hook처럼 값과 쓰기 가능한 함수를 반환한다.

const [count, setCount] = useRecoilState(charCountState);
const [proxy, setProxy] = useRecoilState(proxySelector);

이 때 주의할 점은 selector는 set 함수가 정의되어 있지 않으면 useRecoilState와 함께 사용할 수 없다. 쓰기 가능한 함수가 정의되어 있지 않기 때문이다.

예시

const tempFahrenheit = atom({
  key: 'tempFahrenheit',
  default: 32,
});

const tempCelcius = selector({
  key: 'tempCelcius',
  get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
  set: ({set}, newValue) =>
    set(tempFahrenheit, (newValue * 9) / 5 + 32)
});

const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);

위의 예시는 화씨와 섭씨를 변환하는 atom과 selector다. tempCelcius의 get 함수는 tempFahrenheit의 값을 이용해서 섭씨를 계산하고 set 함수는 입력된 newValue로 화씨를 계산해서 저장한다.

tempCelcius는 tempFahrenheit로부터 파생된 값이기 때문에 tempFahrenheit를 구독하고 있다. 따라서 만약 setTempF 함수를 이용해서 화씨를 변경하면 useEffect와 같은 다른 훅을 사용하지 않아도 tempC가 자동으로 변경된 값이 반영된다.

아쉬운 점

이처럼 Recoil은 State Hook처럼 간편하게 사용할 수 있다. 하지만 아쉬운 점도 존재하기는 한다. 불변성을 지키기 위해서 리스트를 포함한 객체는 새로운 객체를 set 함수에 넘겨줘야 하는데 깊이가 2 이상이 되면 깊은 복사 작업이 굉장히 번거로워진다. Recoil의 개발자들도 atom이라는 이름처럼 원시값이나 간단한 값을 보관하는 것을 권장한다고 안내하고 있다.

...(중략) atom이나 selector들은 구독의 최소 단위이기 때문에 atom들을 원시값이나 간단한 값을 나타내는데 사용하는 것을 권장합니다. 하지만 이러한 사실이 중요하지 않거나 그렇게 하기 번거롭다면 atom들에 풍부한 객체를 저장하셔도 상관 없습니다.
출처

사용자의 아이디나 현재 테마 같은 간단한 값을 저장하기에는 모자람이 없지만 깊이가 2 이상인 복잡한 객체는 Redux-Toolkit의 createSlice가 좀 더 불변성 유지 측면에서 사용하기 편하다.

그래도 Recoil은 아직 생긴 지 얼마 안된 신생 라이브러리(2020년 5월에 첫 커밋)이고 Meta에서 직접 만든 라이브러리이니 앞으로 발전할 여지도 많을 것이라고 본다.


출처

Recoil 공식 문서

좋은 웹페이지 즐겨찾기