본문 바로가기
Frontend/React

Recoil 🌱

by 우보틀 2022. 4. 8.

리액트에서 전역 상태관리를 위해 Redux는 널리쓰입니다.

비동기 action을 지원하기 위해 redux-thunk, redux-saga등이 추가되어 미들웨어로서의 동작으로 비동기 동작을 이용할 수 있게 해줍니다.

Redux에서는 전역 컴포넌트 상태와 서버 상태의 분리를 위해 Redux-thunk query또한 추가 되었습니다.

 

Redux는 리액트를 비롯하여 다른 라이브러리, 프레임워크에서 사용이 가능합니다.

React-Redux는 Redux를 React만을 위해 사용하려고 등장한 라이브러리 입니다.

불필요한 리렌더링을 방지하고 동작을 최적화 하기위해 등장한 것으로 알고 있습니다.

이러한 서드파티 라이브러리 들이 사용되던 시기에 Recoil이 등장합니다.

 

Recoil은 리액트 만을 위해 생겨난 first party 라이브러리 입니다.

메타에서 관리되고 리액트의 라이프 사이클에 직접 관여하여 상태를 직접 다룹니다.

Redux와 다르게 boiler plate 코드가 적고 hook과 같이 사용할 수 있어 기존의 hook의 사용에 익숙한 개발자라면

쉽게 접근할 수 있습니다.

메타에서 관리되기 때문에 리액트의 동시성 모드나 Suspense등 experimental 에서 관리되던 부분도 빠르게 대응이 되지 않을까 기대를 해봅니다.

(Suspense는 React 18이 정식으로 나온 3월 24일 부터 추가되어있습니다. react-dom/client를 import하여 사용할 수 있습니다)

 

저는 전역상태 관리를 위해 Recoil을 사용하고 있고

Recoil의 기본 쓰임새에 대해 알아보고자 합니다.

 


 

Recoil Root

 

Recoil을 사용하기 위해서는 RecoilRoot로 Recoil을 사용하기 위한 컴포넌트를 감싸주어야 합니다.

function App() {
  return (
    <div className="App">
      <SelectorWithCustomHook />
      <hr />
      <SelectorWithContext />
      <hr />
      <RecoilRoot>
        <SelectorWithRecoil />
      </RecoilRoot>
    </div>
  );
}

이렇게 감싸면 RecoilRoot내의 컴포넌트에서 Recoil을 사용할 수 있습니다.

여러개의 RecoilRoot를 사용할 수도 있습니다.

이렇게 될경우 Root내의 컴포넌트 들은 현재 자신에서 가장 가까운 Root를 찾게 됩니다.

또한 각 Root는 독립적인 atom들을 가지게 됩니다.

atom은 각 Root에 따라 다른 값을 가지게 됩니다.

 


atom

atom은 Recoil의 상태를 표현합니다. 

key값으로 구별할 수 있고 default값을 정의할 수 있습니다.

 

atom을 제어할 때 많이 사용하는 훅들 입니다.

useRecoilState : [value, updater]의 형태로 값을 반환해 줍니다.

useRecoilValue : value만 이용할때 사용합니다.

useSetRecoilState : updater만 이용하고자 할때 사용합니다

useResetRecoilState : atom을 초깃값으로 초기화 하고자 할때 사용합니다.

 

atom을 설정할때 promise는 지정할 수 없습니다!!

비동기 값을 설정하고자 할때는 selector를 이용해야 합니다.

 

아래는 atom의 사용예시 입니다.

 

atom

// atom
export const colorState = atom({
  key: "Color",
  default: COLORS.RED,
});

useRecoilState

// useRecoilState
const Header = () => {
  const [selectedColor, setSelectedColor] = useRecoilState(colorState);

  const handleColor = (e: SyntheticEvent) => {
    if (!(e.target instanceof HTMLButtonElement)) {
      return;
    }

    const { color: nextColor } = e.target.dataset;

    if (!nextColor) {
      return;
    }

    setSelectedColor(nextColor);
  };
// 생략
}

 

useRecoilValue

// useRecoilValue

import { colorCounterState } from "../../../atoms";

const ScoreBoard = () => {
  const scores = useRecoilValue(colorCounterState);
  return (
    <>
      {Object.entries(scores).map(([key, value]) => (
        <div>
          {key}: {value}
        </div>
      ))}
    </>
  );
};

 

useSetRecoilState

// useSetRecoilState
const Header = () => {
  const setSelectedColor = useSetRecoilState(colorState);

  const handleColor = (e: SyntheticEvent) => {
    if (!(e.target instanceof HTMLButtonElement)) {
      return;
    }

    const { color: nextColor } = e.target.dataset;

    if (!nextColor) {
      return;
    }

    setSelectedColor(nextColor);
  };
// 생략
}

 

useResetRecoilState

// useResetRecoilState

import { colorCounterState } from "../../../atoms";

const ResetButton = () => {
  const resetScore = useResetRecoilState(colorCounterState);

  const handleReset = () => {
    resetScore();
  };
  return <button onClick={handleReset}> Reset</button>;
};

 

 


atomFamily

atomFamily는 atom을 Map형태로 사용하기 위해 사용하는 util 입니다.

키 값을 넘겨주고 없으면 생성, 있으면 updater를 이용해 상태를 변경할 수 있습니다.

export const boxState = atomFamily({
  key: "BoxState",
  default: COLORS.WHITE,
});

저는 25개의 박스를 관리해야했고 이를 일일히 atom으로 지정할 수도 있었습니다.

하지만 atomFamily util을 사용하여 각각의 개별 상태에 가져올 수 있도록 하였습니다.

id를 인자로 넘겨주어 boxState에서 개별요소만 가져와 상태를 변경해 줄 수 있도록 하였습니다.

const Box = ({ id }: BoxProps) => {
  const [boxColor, setBoxColor] = useRecoilState(boxState(id));
  const selectedColor = useRecoilValue(colorState);

  const clickHandler = () => {
    setBoxColor(selectedColor);
  };
  
}

selector

selector는 Recoil에서 함수나 파생된 상태를 나타냅니다.

atom에서는 비동기 동작을 수행할 수 없어서 selector에서 비동기 동작을 이용하고 비동기 동작안에서 atom을 이용하면 커스터마이징된 데이터를 반환할 수 있습니다.

 

get 함수를 정의할 수 있고 

set 함수를 정의할 수 있습니다.

get 내부에서 atomFamily로 선언한 atom에 키값으로 접근하여 counter 객체에 값을 변경해주고 있는 모습입니다.

export const colorCounterState = selector({
  key: "ColorCounterState",
  get: ({ get }) => {
    const counter: CounterProps = {};
    Object.keys(COLORS).forEach((el) => (counter[el] = 0));

    for (let i = 0; i < BOX_NUM; i++) {
      counter[get(boxState(i))] += 1;
    }

    return counter;
  },
  set: ({ set }) => {
    for (let i = 0; i < BOX_NUM; i++) {
      set(boxState(i), COLORS.WHITE);
    }
  },
});

 

 

각각 get과 set을 이용한 코드 입니다.

// get 이용
import { useRecoilValue } from "recoil";
import { colorCounterState } from "../../../atoms";

const ScoreBoard = () => {
  const scores = useRecoilValue(colorCounterState);
  return (
    <>
      {Object.entries(scores).map(([key, value]) => (
        <div>
          {key}: {value}
        </div>
      ))}
    </>
  );
};

export default ScoreBoard;


// set 이용
import { useResetRecoilState } from "recoil";
import { colorCounterState } from "../../../atoms";

const ResetButton = () => {
  const resetScore = useResetRecoilState(colorCounterState);

  const handleReset = () => {
    resetScore();
  };
  return <button onClick={handleReset}> Reset</button>;
};

export default ResetButton;

selectorFamily

selectorFamily는 selector와 유사하게 사용이 가능합니다.

get, set, selector의 콜백을 매개변수로 전달할 수 있어 다양하게 쓰일 수 있을것 같습니다.

 

매개변수로 숫자를 넘겨주어 파생된 값을 받아오는 것을 볼 수 있습니다.

const myNumberState = atom({
  key: 'MyNumber',
  default: 2,
});

const myMultipliedState = selectorFamily({
  key: 'MyMultipliedNumber',
  get: (multiplier) => ({get}) => {
    return get(myNumberState) * multiplier;
  },

  // optional set
  set: (multiplier) => ({set}, newValue) => {
    set(myNumberState, newValue / multiplier);
  },
});

function MyComponent() {
  // defaults to 2
  const number = useRecoilValue(myNumberState);

  // defaults to 200
  const multipliedNumber = useRecoilValue(myMultipliedState(100));

  return <div>...</div>;
}

 

 

출처: https://recoiljs.org/ko/docs

 

selector(options) | Recoil

Selector는 Recoil에서 함수나 파생된 상태를 나타낸다. 주어진 종속성 값 집합에 대해 항상 동일한 값을 반환하는 부작용이 없는 "순수함수"라고 생각하면 된다. get 함수만 제공되면 Selector는 읽기

recoiljs.org

https://github.com/woobottle/recoil_practice

 

GitHub - woobottle/recoil_practice

Contribute to woobottle/recoil_practice development by creating an account on GitHub.

github.com

 

'Frontend > React' 카테고리의 다른 글

React suspense  (1) 2022.04.10
React 상태관리의 과거, 현재 그리고 미래 (번역글)  (0) 2022.04.09
React 상태관리🌱외 minor concept  (0) 2022.04.07
React css-in-js  (0) 2022.04.06
React form 블로그 따라 다루기  (0) 2022.04.06