React

[React] 상태관리 라이브러리 Recoil에 대해 알아보자

킹우현 2023. 2. 24. 23:07

0) Recoil 이란 ?

Recoil은 React 프로젝트를 위한 많은 전역 상태관리 라이브러리들 중 하나로, 2020년 5월 Facebook에서 출시하였습니다.

그렇기에, 다른 라이브러리(Redux, Mobx)와는 달리 React 전용이며 React에 최적화되어 있다고 할 수 있습니다.

 

Recoil을 통해 전역 상태를 관리하면 코드가 굉장히 간결해지는데, 기존의 context API는 전역 상태를 전달할 때 객체 형태의 value를 사용하기 때문에 객체 안의 값이 하나라도 변경되면 provider로 감싼 모든 하위 컴포넌트들이 리렌더링된다는 단점이 있습니다.


Recoil의 경우 각각의 전역 상태에 대한 atom이 생성되고 해당 상태를 구독하는 구성 요소만 리렌더링 됩니다. 따라서 불필요한 리렌더링을 방지할 수 있습니다.

1) 상태 관리(State management)란 ?

양뱡향 데이터 바인딩을 하는 Angular.js와는 달리, React.js는 단방향 데이터 바인딩 방식의 라이브러리 입니다.

 

즉, 부모 ➡️ 자식 방향으로만 state를 props로 전달할 수 있고, 자식의 props를 부모에게 전달하는 방법은 존재하지 않습니다.

자식 컴포넌트에서 부모 컴포넌트의 state를 변경하기 위해서는 다음과 같은 2가지 방법이 있습니다.

1. 자식에게 부모의 state를 변경할 수 있는 setState 함수를 props로 넘겨준다.
2. 상태관리 라이브러리(Redux, Recoil)을 사용한다.

 

따라서 애플리케이션의 규모가 커지거나 컴포넌트 간의 prop 전달이 깊어질수록 상태의 관리가 어려워지고, state를 필요로 하지 않는 중간의 컴포넌트들 까지도 prop을 받게 된다는 단점이 있습니다.(Props drilling Problem)

 

이러한 Prop drilling을 방지하고자 전역으로 상태관리하는 여러가지 라이브러리들이 등장하였고, 그 중에서 Recoil은 React에 최적화된 상태 관리 라이브러리 입니다. 많은 라이브러리 중에서 Recoil은 다음과 같은 장점들을 가지고 있습니다.

 

1. 전역상태의 설정/정의가 매우 쉽다.
2. Recoil이 지원하는 Hooks로 이를 get/set 하기 때문에 React 문법과 매우 유사하다.
3. 전역상태를 사용하기 위한 Boiler Plate 양이 현저히 적다.(recoil 디렉토리만 필요)

 

 

즉, Hooks 기반으로 매우 심플하며 React와 유사한 사용법 덕분에 러닝커브가 낮다는 장점이 있습니다.

 

2) Recoil Root

// App.js
import React from 'react';
import { RecoilRoot } from 'recoil';

const App = () => {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}
  • Recoil은 내부적으로 context API를 사용하기 때문에, 마찬가지로 Provider가 필요하다.
  • 보통 최상단(App)에 둔다.
  • 중첩이 가능하며, 중첩 된 경우 Recoil API는 가장 가까운 조상 RecoilRoot에 접근한다.

 

3) Recoil의 주요 개념

Recoil을 사용하면 atoms (공유 상태)에서 selector(순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있습니다.

 

Atoms는 컴포넌트가 구독할 수 있는 상태의 단위입니다. Selectors는 atoms 상태 값을 동기 또는 비동기 방식을 통해 변환합니다.

 

또한, Recoil에서 지원하는 Hooks들로 atom 혹은 selecter의 get(접근)set(수정) 등의 다양한 동작이 가능합니다.

 

4) Atom

// /src/recoil/index.ts

import { atom } from 'recoil';

export const focusMemoState = atom<MemoItem>({
  key: 'focusMemo',
  default: null,
})

export const checkedMemoListState = atom<string[]>({
  key: 'checkedMemoList',
  default: [],
})

Atoms는 Recoil 상태의 단위를 의미합니다. 컴포넌트간에 이 상태는 공유되며, 구독 및 업데이트가 가능합니다.

 

unique 한 id인 key로 구분되는 각 atom은, 여러 컴포넌트에서 atom을 구독하고 있다면 그 컴포넌트들도 똑같은 상태를 공유합니다.

특히, atom의 상태가 업데이트되면, 이를 구독하던 컴포넌트들이 모두 리렌더링 됩니다.

 

Atoms을 설정할 땐, Recoil의 atom() 메서드를 통해 변수에 할당해주면 됩니다. 이 때, key, default 2개의 프로퍼티를 필수로 설정해야합니다.

  • key : 고유한 key 값 (보통 해당 atom을 생성하는 변수 명으로 지정합니다.)
  • default : atom 의 초기값을 정의합니다. 정적인 값(int, string...), promise, 다른 atom 의 값으로 설정할 수 있습니다.

 

5) 전역상태 관련 Hooks

전역상태(Atoms, Selector)를 get/set 하기 위해 Recoil에서 제공하는 Hooks들을 사용할 수 있습니다. 기본적으로 아래 4가지가 크게 사용됩니다.

  • useRecoilState() : useState() 와 유사하다. [state, setState] 튜플에 할당하며, 인자에 Atoms(혹은 Selector)를 넣어준다.
  • useRecoilValue() : 전역상태의 state 상태값만을 참조하기 위해 사용된다. 선언된 변수에 할당하여 사용하면 된다.
  • useSetRecoilState() : 전역상태의 setter 함수만을 활용하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.
  • useResetRecoilState() : 전역상태를 default(초기값)으로 Reset 하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.

 

6) Selector

export const memoListSelector = selector<MemoList>({
  key: 'memoList',
  get: async ({ get }) => {
    const id = get(focusLabelState)?.id;
    const totalList = get(totalMemoListSelector);
    return id ? getMemoListByLabel(id) : totalList;
  }
})

Selector는 atom 혹은 다른 Selector 상태를 입력받아 동적인 데이터를 반환하는 순수함수(Pure Function) 입니다.

상태값에서 비롯된 파생된 데이터를 만들 때 사용되며, atom처럼 컴포넌트가 이를 구독할 수 있습니다. (readonly 값이므로 useRecoilValue)

 

Selector가 참조하던 다른 상태가 변경되면 이도 같이 업데이트되며, 이 때 Selector를 바라보던 컴포넌트들이 리렌더링 됩니다.

 

Selector를 설정할 때도, Recoil의 selector() 메서드를 통해 등록하면 됩니다. 기본적으론, key와 get 2개의 프로퍼티를 설정합니다.

  • key : 고유한 key 값
  • get : Selector 순수함수사용할 값을 반환하며, 매개변수인 콜백객체 내 get() 메서드로 다른 atom 혹은 selector를 참조한다.
  • set : 쓰기 가능한 Selector
const proxySelector = selector({
  key: 'ProxySelector',
  get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
  set: ({set}, newValue) => set(myAtom, newValue),
});

Selector에 set을 설정하게 되면, 쓰기 가능한 모드로 변경됩니다. set은 콜백객체, 새로운 값 2가지를 각각 매개변수로 받습니다.

콜백객체 내에는 마찬가지로 set() 메서드가 존재하며, 이는 다른 atom들을 새로운 값으로 세팅하기 위해 사용됩니다.

 

이처럼, set() 메서드는 상태변수, 2가지를 매개변수로 받습니다. 또한, 해당 상태값을 newValue로 갱신해주는 역할을 합니다.

컴포넌트에서 이 set을 사용할때는, useRecoilState() Hooks로 Selector를 가져오면 setter 함수가 이 set()의 역할을 하게 됩니다.

 

* 비동기 Selector

 

API 등 비동기 요청을 한 데이터를 전역상태에 넣는 경우가 종종 있습니다. 이 때, 이 비동기 호출을 내부에 설정해 상태값으로 반환하는 역할 역시 Selector가 충실하게 수행해줍니다 😎