React - Recoil 간략 가이드

milkbottle·2024년 8월 17일
0

React

목록 보기
31/33

Recoil

React에서 유명한 상태관리 라이브러리인 Recoil에 대해 알아보도록 하겠다.
상태관리하는 이유는 props drilling 현상을 최대한 줄이기위해 탄생했다고 해도 과언이 아니다.

Props Drilling

부모 컴포넌트에서 자식 컴포넌트로 상태를 전달하기 위해서는 props로 전달을 해야한다.
React는 계층적인 UI 구조를 가지기 때문에 자식이 28대손까지 있다면 Grand Grand Grand Grand .... Parents의 props를 받는 구조가 될 수도 있다.

그래서 React에서는 Context API를 만들었지만, 이는 관리하기도 귀찮아서 잘 사용하지 않는다.
원리 자체는 Recoil과 거의 유사하다.
하지만 상태 하나하나를 만들때마다 App 컴포넌트의 JSX에서 <~~~Context.Provider>가 증식하는 현상을 볼 수 있을 것이다.

그래서 Recoil에서는 이들을 묶어서 <RecoilRoot>이라는 태그로 한번에 여러 상태들을 전역으로 관리할 수 있도록 한다.

사용법

스플래시 화면에서 로그인과 회원가입 버튼이 네비게이션바로 있는 상황을 예제로 들자.

로그인하기전에는 로그인과 회원가입이 나오며, 로그인 후에는 로그아웃 버튼만 보인다.
그리고 로그인이나 회원가입 버튼을 누르면 각 버튼에 맞는 모달창이 나오는 정책이라고 하자.

RecoilRoot

import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import GlobalStyles from './styles/GlobalStyles';
import { lightTheme } from './styles/theme';
import { Splash } from './ui/Splash';
import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <ThemeProvider theme={lightTheme}>
        <GlobalStyles />
        <Routes>
          <Route path="/" element={<Splash />} />
        </Routes>
      </ThemeProvider>
    </RecoilRoot>
  );
}

export default App;

예시의 코드이다. 다른건 다 무시하고, RecoilRoot로 최상위 JSX를 감싸주면 된다.
이렇게 되면 하위에 있는 모든 컴포넌트는 Recoil의 상태들을 가져와 사용할 수 있다.

상태 선언법 - atoms

Recoil에서는 상태들을 atoms로 관리한다.
key, default로 키 값과 기본값을 정의할 수 있다.

import { atom } from 'recoil';

// 로그인 상태를 저장하는 atom (초기값: 로그인되지 않은 상태)
export const loginState = atom<boolean>({
  key: 'loginState', // 고유한 키
  default: false, // 초기값: 로그인되지 않은 상태
});

// 모달 상태를 저장하는 atom (초기값: 모달이 닫혀 있는 상태)
export const modalState = atom<string | null>({
  key: 'modalState',
  default: null, // 초기값: 모달이 닫혀 있는 상태
});

스플래시 화면에서 필요한 상태는 2가지 이다.

  • 어떠한 모달창을 눌렀는지(회원가입 / 로그인 / 아무것도 안한 상태)
  • 로그인 여부

상태 변경법 - useRecoilState

상태값을 변경을 할 수 있어야한다.
useState를 사용했을 때는 const [state, setStae] = useState()로 상태와 변경하는 기능이 둘 다 제공되는 것처럼, Recoil에도 유사한 기능이 있는 훅이 있다.
useRecoilState으로 다음처럼 사용할 수 있다.

import { useRecoilState } from 'recoil';
import { modalAtom, loginAtom } from '../recoil/splash';

function MyComponent() {
  // 2. Recoil 상태 사용
  const [modalState, setModalState] = useRecoilState(modalAtom);
  const [loginState, setLoginState] = useRecoilState(loginAtom);

  return (
    // modalState, loginState를 활용
  );
}

또 다른 훅들 - useRecoilValue, useSetRecoilState

useRecoilState에서는 state, setState를 둘다 제공하는데 이 훅은 두개를 분리해서 제공한다.

또다른 상태 선언법 - selector

단순한 값만 가지는 atoms와는 다르게 set과 get을 포함하는 상태를 선언할 수 있다.
이를 selector라고 하는데, 마찬가지로 key가 존재하며 추가적으로 set, get을 선택적으로 정의할 수 있다.

import { selector } from 'recoil';
import { loginState, modalState } from './atoms';

// 로그인 상태를 가져오는 selector
export const loginStateSelector = selector<boolean>({
  key: 'loginStateSelector',
  get: ({ get }) => {
    const isLoggedIn = get(loginState);
    return isLoggedIn;
  },
  set: ({ set }, newValue) => {
    set(loginState, newValue);
  },
});

// 모달 상태를 가져오는 selector
export const modalStateSelector = selector<boolean>({
  key: 'modalStateSelector',
  get: ({ get }) => {
    const isModalOpen = get(modalState);
    return isModalOpen;
  },
  set: ({ set }, newValue) => {
    set(modalState, newValue);
  },
});

atoms를 가져와서 selector로 확장한 것이다.
selector도 atoms와 마찬가지로 useRecoilState, useRecoilValue 등의 훅에서 사용이 가능하다.

왜 굳이 selector를 그러면 쓰는걸까?

위의 selector 코드를 보면 get, set이 그대로 정의되어있어서 사실 atoms만 써도 잘 동작할 것처럼 보인다.
왜 굳이 selector를 쓰는걸까?

예를들어 쇼핑카트가 있고 총합을 계산해야하는 상황이 있다고 하자.

import { atom, selector } from 'recoil';

// 쇼핑 카트의 각 상품을 나타내는 atom
export const cartItemsState = atom({
  key: 'cartItemsState',
  default: [
    { id: 1, name: 'Apple', price: 1.2, quantity: 2 },
    { id: 2, name: 'Orange', price: 0.9, quantity: 3 },
  ],
});

// 총합을 계산하는 selector
export const totalPriceSelector = selector({
  key: 'totalPriceSelector',
  get: ({ get }) => {
    const cartItems = get(cartItemsState);
    return cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
  },
});

이렇게 쇼핑카트의 상태는 atoms로 저장하고, 파생된 상태인 쇼핑카트에 담긴 가격의 합을 selector로 정의할 수 있다.
마치 useMemo 훅과 유사하다고 보면 쉽다.

0개의 댓글