CS 대비/FrontEnd Part

[React] 전역 상태 관리 - Recoil

JellyApple 2023. 11. 3. 15:40

이번에는 Redux와 더불어 자주 쓰이는 Recoil에 대해 알아보고자 한다. 

1. Recoil

Recoil은 Redux의 보일러플레이트 코드 단점 문제를 개선 한 라이브러리이다. Redux는 직관적이지만 하나의 상태를 관리하더라도 많은 코드가 필요하기 때문에 Redux Toolkit이 나왔지만 그럼에도 많은 코드의 소모를 가져왔고 이에 대한 하나의 대안으로 나온 라이브러리가 바로 Recoil이다. 

0) 설치

$ npm i recoil @types/recoil
// src/index.tsx

import { createRoot } from "react-dom/client";
import { RecoilRoot } from "recoil";
import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement!);

root.render(
  <div>
    <RecoilRoot>
        <App />
      </ThemeProvider>
    </RecoilRoot>
  </div>
);


1)  Atom 

An atom represents state in Recoil. The atom() function returns a writeable RecoilState object
- Recoil 공식 문서

 

atom은 상태를 나타내고 Redux의 store와 유사하다. 그래서 atom이 업데이트 되면, 해당 atom을 구독하고 있던 모든 컴포넌트들의 state 즉 상태가 리렌더링된다.  Atom의 id는 고유한 값으로 구분되어야 하고 여러 컴포넌트에서 atom을 구독 하고 있으면 그 컴포넌트들도 똑같은 상태를 공유한다.  atom은 아래 코드와 같이 사용 가능하다.

// recoilState.ts
export const recoilState = atom({
  key : 'recoilState',
  default: []
  });
page.tsx
import React from 'react'
import { recoilState } from '../recoilState'
import { useRecoilState} from 'recoil'

const page = () => {
   const [ name , setName] = useRecoilState(recoilState);
   
   return(
       <div>
         Recoil
        </div>
        );
      }
 export default Page;

 

이렇게 코드를 작성하면 어디서든 전역으로 받아올 수 있고 어떤 컴포넌트에서든 state를 변경할 수 있다. 물론 하나의 컴포넌트에서 상태를 변경해도 이를 구독하고 있는 모든 컴포넌트들의 상태 값이 리렌더링 된다. 

 

2) Selectors 

atoms 이나 다른 selectors를 입력으로 받아들이는 순수 함수다. 상위의 atoms 또는 selectors가 업데이트 되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링 된다.

 

컴포넌트의 관점에서 보면 selectors와 atoms는 동일한 인터페이스를 가지므로 서로 대체할 수 있다.

 

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

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

 

- get : 계산될 함수다. 전달되는 get 인자를 통해 atoms와 다른 selectors에 접근할 수 있다. 참조했던 다른 atom이나 selectors가 업데이트 되면 이 함수도 다시 실행된다. 

 

Selectors는 useRecoilValue()를 사용해 읽을 수 있다. useRecoilValue()는 하나의 atom이나 selector를 인자로 받아 대응하는 값을 반환한다. 

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>
    </>
  );
}

 

3) 비동기 처리 (Suspense, Loadable) 

selector을 이용하여 처리 가능하다. 

// MyComponent.js
import React, { useEffect } from 'react';
import { atom, useRecoilState, RecoilRoot } from 'recoil';

// Recoil 상태 정의
const dataState = atom({
  key: 'dataState',
  default: 'No data', // 초기값
});

// 비동기 데이터 가져오는 함수 (간단하게 시간 지연으로 흉내냄)
const fetchData = async () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Fetched data');
    }, 1000);
  });
};

const MyComponent = () => {
  const [data, setData] = useRecoilState(dataState);

  // 버튼 클릭 시 데이터를 비동기로 가져오는 함수
  const handleFetchData = async () => {
    const fetchedData = await fetchData();
    setData(fetchedData); // Recoil 상태 업데이트
  };

  return (
    <div>
      <h1>Recoil을 사용한 간단한 비동기 처리</h1>
      <p>Data: {data}</p>
      <button onClick={handleFetchData}>데이터 가져오기</button>
    </div>
  );
};

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

export default App;