[Recoil] React에서 Recoil 도입기 (Context API와의 비교, 특징, 장점, 기본 개념)

Janet·2023년 12월 27일
0

React

목록 보기
24/26
post-custom-banner

Recoil 도입기


  • 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링 되는 효과를 발생시키기도 한다.
  • 전역적인 상태 관리를 위해 사용을 고려하게 되는 라이브러리, 기존에 Redux를 사용해보고, 또 Redux의 boilerplate를 개선한 Redux-toolkit을 사용했었다. 사이드 프로젝트와 과제 테스트를 수행하면서 페이스북에서 만든 React 친화적인 상태관리 라이브러리 Recoil을 사용해보기로 했다. (store와 같은 외부 요인이 아닌 React 내부의 상태를 활용하고 Context API를 기반으로 구현되어있기 때문에 다른 상태 관리 라이브러리에 비해 상대적으로 리액트에 친화적인 라이브러리라고 한다.)
  • 그 전에, 라이브러리 사용을 최소화하기 위해 React 자체 내장된 Context API도 고려해봤다.

Context API: Context API는 props를 사용하지 않아도 특정 값이 필요한 컴포넌트끼리 쉽게 값을 공유할 수 있게 해준다. 다만, 공유하고자하는 state가 있는 컴포넌트를 Provider로 감싸게 되는데, 상태값이 변경되면 Provider로 감싼 모든 자식 컴포넌트들이 re-rendering되므로 전역 상태 관리를 위한 도구가 아닌, 데이터를 쉽게 전달하고 공유하기 위한 목적으로 사용하는 것이 적합하다. 따라서 테마 데이터, 언어 혹은 지역 데이터 등과 같이 변경이 자주 일어나지 않는 데이터를 다룰때 용이하다.

Recoil의 특징 및 장점

  • Recoil은 Context API 기반으로 구현된 함수형 컴포넌트에서만 사용 가능한 라이브러리이다.
  • Recoil은 컴포넌트 단위로 상태를 관리할 수 있다. 이는 전역 상태를 사용할 때 컴포넌트 간 의존성을 낮추어 깔끔한 컴포넌트 아키텍처를 유지할 수 있음을 의미한다.
  • Recoil은 변경된 Atom의 상태를 공유하고 있는 컴포넌트만 리렌더링 되므로, 쓸모없는 리렌더링이 계속 일어나지 않는다.
  • React hook과 비슷한 형태로 사용가능하며, atom과 selector와 같은 기본 개념을 숙지한다면 어렵지 않게 구현이 가능하여 러닝커브가 Redux에 비해 상대적으로 낮다.
  • 비동기 처리를 기반으로 작성되어 동시성 모드(Concurrent Mode)를 제공하기 때문에, Redux와 같이 다른 비동기 처리 라이브러리(redux-thunk나 redux-saga와 같은 미들웨어)에 의존할 필요가 없다. (Recoil에서 selector를 이용하여 비동기 액션에 대한 처리 가능)
    • cf. Concurrent Mode : 흐름이 여러 개가 존재하는 경우에 리액트에서 렌더링의 동작 우선순위를 정하여 적절한 때에 렌더링해준다.
      • cf. Concurrency(동시성): 동시성이란 하나의 프로그램이 동시에 여러 작업을 처리하도록 하는 것을 의미하는데, 병렬성(여러 작업이 동시에 실제로 동작)과는 다르게 동시성은 여러 작업이 동시에 진행되는 것처럼 보이지만, 실제로는 각 작업이 작은 단위로 번갈아 가면서 실행되는 것을 말한다.

Recoil 설치

  • npm: npm install recoil
  • yarn: yarn add recoil

Recoil 기본 개념 - RecoilRoot

recoil 상태를 사용하는 컴포넌트는 부모 트리에 RecoilRoot가 필요하다. 전역적인 상태관리를 위해 루트 컴포넌트에 RecoilRoot를 넣어준다. Redux를 사용할 때 루트에 Provider 태그로 감싸주는 것처럼 Recoil은 RecoilRoot를 감싸주면 된다.

// App.js

import { RecoilRoot } from "recoil";
import Home from "./components/Home";

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

export default App;

Recoil 기본 개념 - Atoms

  • Atoms는 Recoil에서 가장 기본적인 상태의 단위업데이트와 구독이 가능하다. 이는 컴포넌트들 간에 공유되는 상태를 의미하며, 해당 상태를 수정하기 위해서는 Recoil의 useRecoilState 훅을 사용한다. atom의 값을 변경하면, 그것을 구독하고 있는 컴포넌트들이 모두 다시 렌더링된다. atom을 생성하기 위해 고유한 key 값과 default값을 설정해야 한다.
// atoms.js

import { atom } from 'recoil';

// 예시: 사용자 이름을 담는 atom
export const userNameState = atom({
  key: 'userNameState',
  default: '',
});

Recoil 기본 개념 - Selectors

  • Selectors는 Atoms나 다른 Selectors를 입력으로 받는 순수 함수이다. selector는 atom의 값을 변형하거나 파생된 상태를 계산하는 데 사용된다. selector를 정의할 때는 get 함수만 사용하면 읽기 전용이며, get 함수를 통해 atom의 값을 가져와서 변형한 값을 반환(RecoilValueReadOnly 객체 반환)한다. atom을 쓰기(업데이트)할 수 있는 set 함수를 옵션으로 받을 수 있다.
// selectors.js

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

// 예시: 대문자로 변환된 사용자 이름을 반환하는 selector
export const upperCaseUserNameSelector = selector({
  key: 'upperCaseUserNameSelector',
  get: ({ get }) => {
    const userName = get(userNameState);
    return userName.toUpperCase();
  },
});
  • Selectors는 비동기 처리 뿐만 아니라 데이터 캐싱 기능도 제공하기 때문에 비동기 데이터를 다루기에 용이하다. (아래 예시 코드 참고)
// CurrentUserInfo.js

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}
  • Recoil은 비동기 호출에 대한 보류중인 데이터(pending 상태)를 다루기 위해 React Suspense와 함께 동작하도록 디자인되어 있다. 컴포넌트를 Suspense로 감싸는 것으로 아직 보류중인 하위 항목들을 잡아내고 대체하기 위한 UI를 렌더한다.
import { RecoilRoot } from "recoil";
import Home from "./components/Home";

function App() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
      	<Home />
      </React.Suspense>
    </RecoilRoot>
  );
}

export default App;

Recoil Hooks

  • useRecoilState : atom의 값을 구독하여 업데이트할 수 있는 hook으로, 첫 요소가 상태의 값이며, 두번째 요소가 호출되었을 때 주어진 값을 업데이트하는 setter 함수를 반환한다. 리액트의 useState와 동일한 방식으로 사용할 수 있다.
  • useRecoilValue : setter 함수 없이 atom의 값을 반환만 한다.
  • useSetRecoilState : setter 함수만 반환한다.
  • 위 세가지 hooks를 자주 사용하고, 이외에도 useResetRecoilState(), useRecoilStateLoadable() 등 더 많은 hooks들이 있다.

Recoil Hooks 예시 코드

// atoms.js

import { atom } from 'recoil';

export const countState = atom({
  key: 'countState',
  default: 0,
});
// Component.js

import { useRecoilState, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil';
import { countState } from './atoms';

const Component = () => {
  const [count, setCount] = useRecoilState(countState); // 상태를 읽고 쓰는 훅
  const currentCount = useRecoilValue(countState); // 상태를 읽는 훅
  const setCountWithUpdater = useSetRecoilState(countState); // 상태를 설정하는 훅
  const resetCount = useResetRecoilState(countState); // 상태를 초기화하는 훅

  // 현재 값에 1을 더하여 recoil 상태 업데이트
  const handleIncrement = () => setCount(count + 1);

  // 현재 값에 1을 빼서 recoil 상태 업데이트
  const handleDecrement = () => setCount(count - 1);
 
  // recoil 상태를 초기화
  const handleReset = () => resetCount();

  const handleAsyncUpdate = () => {
    // 비동기로 recoil 상태 업데이트
    setTimeout(() => {
      setCountWithUpdater((prevCount) => prevCount + 2);
    }, 1000);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Current Count (useRecoilValue): {currentCount}</p>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
      <button onClick={handleReset}>Reset</button>
      <button onClick={handleAsyncUpdate}>Async Update</button>
    </div>
  );
};

export default Component;

Reference.

profile
😸
post-custom-banner

0개의 댓글