본문 바로가기
  • 살짝 구운 김 유나
Web/React

React를 위한 상태관리 라이브러리 - Recoil

by yunae 2023. 9. 12.

 

https://recoiljs.org/ko/docs/introduction/installation

 

설치 | Recoil

NPM

recoiljs.org

 

Recoil?

2020년에 Facebook에서 발표한, React 전용 상태관리 라이브러리이다.

Recoil을 통한 상태관리는 아주 간결하다는 장점이 있다.

 

Redux의 경우

Flux 패턴을 사용하여 안정적으로 상태관리가 가능하지만,

  • React 전용 라이브러리가 아니다.
  • 초기 세팅이 요구된다.
  • 비동기 데이터를 사용하려면 추가적인 라이브러리 설치가 필요하다.
  • 많은 양의 **보일러플레이트 코드를 작성해야 한다.

는 단점을 가지고 있기도 하다. Recoil은 이러한 단점을 보완한다.

** Boilerplate Code : 변화 없이 여러 군데에서 반복되는 코드

 

Why Recoil

  • 전역 상태 관리를 컴포넌트 내부의 상태 관리처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다.
  • Concurrent Mode를 비롯한 다양한 React 기능들과 호환이 가능하다.
  • 상태 정의에 대한 코드 분할이 가능하다.
  • 전역 상태를 사용하고 있는 컴포넌트는 수정 없이 변경된 상태로 교체 가능하다.
  • 컴포넌트 수정 없이 동기/비동기 전환이 가능하다.
  • 전역 상태의 경우 애플리케이션이 변경되더라도 그대로 유지된다.

 

Recoil 시작하기

npm install recoil
// or
yarn add recoil

 

설치 후, 루트 컴포넌트에 RecoilRoot를 추가한다.

import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}

 

<RecoilRoot ...props>

Recoil hooks를 사용하는 모든 구성 요소의 조상 컴포넌트이다. 여러개의 루트가 존재할 수 있다.

  • initializeState?: (MutableSnapshot => void)
    • MutableSnapshot을 사용해서 <RecoilRoot>의 상태를 초기화한다.
    • 초기 렌더링에 대한 상태를 설정하는 것이지 비동기적인 초기화를 위한 것(useSetRecoilState(), useRecoilCallback())은 아니다.
  • override?: boolean
    • <RecoilRoot>가 다른 <RecoilRoot>와 중첩된 경우 사용된다.
    • true => 해당 루트는 새로운 Recoil 범위를 생성한다.
    • false => 해당 루트는 자식 렌더링만 수행하므로, 이 루트의 자식 컴포넌트는 가장 가까운 루트의 Recoil 값에 접근한다.

여러 <RecoilRoot>를 사용하는 경우 각각 독립적인 루트가 된다.

atom은 각 루트에 따라 다른 값을 갖게 되는 것이다. 이러한 동작은 override를 false로 지정하지 않는 한, 루트가 다른 루트에 중첩될 때 동일하게 발생된다.

 

 

Recoil 주요 개념

 

Atoms

atom은 Recoil의 상태를 표현한다. 함수는 쓰기가 가능한 RecoilState 객체를 반환한다.

function atom<T>({
  key: string,                                       // atom을 식별하기 위한 고유한 문자열
  default: T | Promise<T> | RecoilValue<T>,          // 상태 초기 값

  effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,  

  dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
  • Atom이 업데이트되면 각각 구독된 컴포넌트는 리렌더링된다.
  • Atoms는 런타임에서 생성될 수도 있다.
  • Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다.
  • 동일한 Atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
// Atoms는 atom 함수를 사용해 생성한다.
const fontSizeState = atom({
    // 모든 atoms의 map을 볼 수 있는 API에 사용되는 고유한 key가 필요하다.
    // 두 개의 atom이 같은 키를 갖는 것은 오류이기 때문에 키 값은 전역적으로 고유해야 한다.
    key: 'fontSizeState',
    default: 14,
});

 

컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용한다.

React의 useState와 비슷하지만, 상태가 컴포넌트 간에 공유된다는 차이점이 있다.

아래의 예제에서 버튼을 클릭하면 버튼의 글꼴 크기가 1만큼 증가하며 , fontSizeState atom을 사용하는 다른 컴포넌트의 크기도 같이 변화한다.

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

//-------------------------------------------------------------------------------

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

 

자주 사용되는 Hooks - atom

setRecoilState(state)

상태의 값과, 상태의 값을 업데이트하는 setter 함수를 반환한다.

  • Recoil 상태를 인수로 받는다는 점을 제외하고는 useState() hook과 비슷하다.
  • 상태가 업데이트 되었을 때 리렌더링 된다.
function useRecoilState<T>(state: RecoilState<T>): [T, SetterOrUpdater<T>];

type SetterOrUpdater<T> = (T | (T => T)) => void;

 

useRecoilValue(state)

주어진 상태의 값을 반환한다. atom을 읽을 때에 사용한다.

function useRecoilValue<T>(state: RecoilValue<T>): T;

 

useSetRecoilState(state)

Recoil의 상태 값을 업데이트하기 위한 setter 함수를 반환한다.

function useSetRecoilState<T>(state: RecoilState<T>): SetterOrUpdater<T>;

type SetterOrUpdater<T> = (T | (T => T)) => void;

 

useResetRecoilState(state)

주어진 상태를 default 값으로 리셋하는 함수를 반환한다.

function useResetRecoilState<T>(state: RecoilState<T>): () => void;

 

 

Selectors

Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수함수이다.

쉽게 말하면 기존의 atom을 처리하여 새로운 값을 리턴하거나 수정하는 역할을 수행한다. (computed,,와 같은 역할을 하는 것 같다.)

selectors에 명시한 함수를 통해서 데이터를 계산하고, 최소한의 상태만 atoms로 관리한다.

get 함수만 작성되면 읽기만 가능한 RecoilValueReadOnly 객체를 반환하고, set 함수도 작성되면 쓰기 가능한 RecoilState 객체를 반환한다.

function selector<T>({
  key: string,                          

  get: ({                               
    get: GetRecoilValue
  }) => T | Promise<T> | RecoilValue<T>,

  set?: (
    {
      get: GetRecoilValue,
      set: SetRecoilState,
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) => void,

  dangerouslyAllowMutability?: boolean,
})
  • get : 파생된 상태의 값을 평가하는 함수이다.
    • 값을 직접 반환하거나 Promise, atom, selector를 반환할 수 있다.
  • set? : set이 설정되면 selector는 쓰기 가능한 상태를 반환한다.
    • get => 다른 atom이나 selector로부터 값을 가져오는 함수
    • set => Recoil 상태의 값을 설정할 때 사용되는 함수

 

예시)

// 인자로 받은값을 set 하는 방법
const proxySelector = selector({
  key: 'ProxySelector',
  get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
  set: ({set}, newValue) => set(myAtom, newValue),
});

// 조건에 따라 다른 값을 반환하는 방법
const transformSelector = selector({
  key: 'TransformSelector',
  get: ({get}) => get(myAtom) * 100,
  set: ({set}, newValue) =>
    set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});

상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다.

컴포넌트들은 selectors를 atoms처럼 구독할 수 있고, 구독하고 있던 selectors가 변경되면 다시 렌더링된다.

 

컴포넌트의 관점에서는 Selectors와 atoms가 동일한 인터페이스를 가지기 때문에 서로 대체할 수 있다.

예시) 온도 변환

import {atom, selector, useRecoilState} from 'recoil';

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

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

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

  const addTenCelsius = () => setTempC(tempC + 10);
  const addTenFahrenheit = () => setTempF(tempF + 10);

  return (
    <div>
      Temp (Celsius): {tempC}
      <br />
      Temp (Fahrenheit): {tempF}
      <br />
      <button onClick={addTenCelsius}>Add 10 Celsius</button>
      <br />
      <button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
    </div>
  );

 

연습하기

그냥,,,빵먹고 싶어서 실습한 사람,,,,총 2만 2천언 대것습니다,,.,

// App.js
import { useRecoilState, useRecoilValue } from "recoil";
import { donutCnt, saltyCnt, totalPriceSelector } from "./recoil";

function App() {
  const [salty, setSalty] = useRecoilState(saltyCnt);
  const [donut, setDonut] = useRecoilState(donutCnt);
  const totalPrice = useRecoilValue(totalPriceSelector);

  return (
    <div className="App" style={{ padding: "20px" }}>
      <h3>소금빵이 먹고싶어요</h3>
      <h4>소금빵은 개당 3000원이에요@@</h4>
      <h4>도넛은 개당 2500원이에요@@</h4>
      <div style={{ display: "flex", gap: "15px" }}>
        <h5>소금빵 몇개?</h5>
        <input
          onChange={(e) => {
            setSalty(e.target.value);
          }}
          type="number"
          placeholder="갯수를 입력하세요"
        />
      </div>
      <div style={{ display: "flex", gap: "15px" }}>
        <h5>도넛 몇개?</h5>
        <input
          onChange={(e) => setDonut(e.target.value)}
          type="number"
          placeholder="갯수를 입력하세요"
        />
      </div>
      <div style={{ display: "flex", gap: "15px" }}>
        <h5>
          소금빵 {salty}개와 도넛 {donut}개의{" "}
        </h5>
        <h5>총 가격이에요 : {totalPrice} </h5>
      </div>
    </div>
  );
}

export default App;
// recoil.js

import { atom, selector } from "recoil";

export const saltyCnt = atom({
  key: "saltyCnt",
  default: 0,
});

export const donutCnt = atom({
  key: "donutCnt",
  default: 0,
});

export const totalPriceSelector = selector({
  key: "totalPrice",
  get: ({ get }) => {
    const salty = get(saltyCnt);
    const donut = get(donutCnt);
    return (salty * 3000 + donut * 2500);
  },
});

 

 

 

 

벌써 가을바람 냄새가! 여기는 구름 맛집이에요 :)

 

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

[React] i18n으로 다국어 지원하기 (i18next, react-i18next)  (0) 2023.10.22
[React] React Query  (0) 2023.01.18
[React] Redux  (0) 2022.12.19
[React] ajax  (0) 2022.12.15
[React] Lifecycle  (0) 2022.12.15

댓글