본문 바로가기

FrontEnd+

Recoil 입문 - 기초 API 알아보기

 

Recoil은 리액트 전용 상태 관리 라이브러리이다.

리액트에서는 본인 또는 부모의 상태에만 접근 가능하다는 점 때문에 코드 복잡도가 올라가거나 불필요한 리렌더링이 발생하기도 한다. Recoil은 리액트스러운 메커니즘을 유지하면서 이러한 단점을 극복하기 위해 등장했다고 한다.

Recoil의 가장 작은 단위인 아톰(atoms)부터 순수함수인 셀렉터(selectors)까지 하나씩 알아보자.

 

 

📦 아톰 (atoms)

아톰은 상태(state)의 단위이다. 1 아톰 1 상태라고 생각하면 된다.

컴포넌트는 아톰의 변화를 구독(subscribe)할 수 있다. 아톰이 업데이트 되면, 아톰을 구독하고 있던 컴포넌트들은 새로운 아톰의 값을 반영하여 리렌더(re-render)된다.

만약 아톰을 여러 컴포넌트에서 사용하고 있더라도, 상태는 문제없이 공유된다.

아톰은 recoil에서 atom 함수를 가져와서 만들면 된다. 각 아톰은 앱 전체에서 고유한 key를 가져야 한다는 점을 주의하자. default에는 초기값을 넣어주자.

import { atom } from 'recoil';

const fontSizeState = atom({
  key: 'fontSizeState',
  default: 14,
});

 

컴포넌트에서 아톰의 값을 읽거나 수정하고 싶다면, useRecoilState 훅을 사용하면 된다. 리액트의 useState와 사용하는 방법이 매우 유사한데, 컴포넌트 트리 구조에 관계없이 다른 점은 어느 컴포넌트에서나 사용할 수 있다는 점이 다르다.

import { useRecoilState } from 'recoil';

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  
  return (
    <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}

 

만약 버튼을 클릭해서 폰트 사이즈를 증가시켰다면 fontSize 아톰을 동일하게 사용하고 있는 다른 컴포넌트에도 이 내용이 반영된다.

function Text() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return <p style={{fontSize}}>This text will increase in size too.</p>;
}

 

 

🖍 셀렉터(selector)

셀렉터는 아톰이나 다른 셀렉터를 받는 순수함수이다. 아톰이나 셀렉터가 변경되면, 이 셀렉터 함수가 다시 평가된다.

컴포넌트는 아톰을 구독할 수 있듯이 셀렉터도 구독해둘 수 있다. 셀렉터를 구독해두면, 셀렉터가 변경되었을 때 마찬가지로 리렌더된다.

셀렉터는 상태값으로 뭔가 계산해낼 때 사용할 수 있어서 중복된 상태를 마구마구 생산하는 것을 방지해준다. 딱 필요한 상태만 아톰으로 만들어두고, 셀렉터로 아톰을 적절하게 활용하면 된다. 

셀렉터는 recoil에서 selector함수를 가져와서 만들면 된다. 계산하는 내용을 get 메서드에다가 작성하면 되는데, 다른 아톰이나 셀렉터를 가져오려면 인자로 { get } 을 받아서 get()으로 접근하면 된다. get()으로 접근하고 있는 아톰이나 셀렉터가 업데이트 되면, 이 셀럭터를 구독하고 있는 컴포넌트도 당연히 리렌더된다.

import { selecotr } from 'recoil';

const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) => {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});

 

셀렉터를 가져오려면 useRecoilValue를 쓰면 된다. 아톰이나 셀렉터를 인자로 넣어주면 된다. 셀렉터의 경우 변경할 수 없기 때문에 useRecoilState로 가져와서 사용할 수는 없다. 반대로 아톰의 경우 변경하려는 것이 아니라면 useRecoilValue로 가져와서 쓸 수 있다.

import { useRecoilState, useRecoilValue } from 'recoil';

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);

  return (
    <>
      <div>Current font size: {fontSizeLabel}</div>

      <button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    </>
  );
}

 

하나의 셀렉터로 필요한 여러 개의 값을 한번에 리턴해줄 수도 있다.

const todoListStatsState = selector({
  key: 'todoListStatsState',
  get: ({get}) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
    const totalUncompletedNum = totalNum - totalCompletedNum;
    const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum * 100;

    return {
      totalNum,
      totalCompletedNum,
      totalUncompletedNum,
      percentCompleted,
    };
  },
});

 

 

 

참고자료

recoil 공식문서