React - Recoil

김서영·2024년 6월 13일
0

CS 스터디 - React

목록 보기
28/28

Recoil


페이스북에서 만든 React 상태 관리 라이브러리

React 애플리케이션에서 전역 상태를 관리하고, 복잡한 상태 로직을 단순화하기 위해 설계되었다. React의 기본 상태 관리 메커니즘과 긴밀하게 통합되어 있으며, 상태를 효율적으로 공유하고 관리할 수 있는 기능을 제공한다.

설치 방법

npm install recoil

Rocoil의 주요 개념

1. Atoms

  • Recoil의 가장 작은 상태 단위로, 상태를 저장하는 데 사용된다.
  • Atom의 값이 변경되면 이를 구독하는 모든 컴포넌트가 자동으로 리렌더링된다.
  • Atom은 React의 상태처럼 사용되며, useRecoilState 훅을 통해 접근할 수 있다.
// Atom 정의
import React from 'react';
import { atom } from 'recoil';

const textState = atom({
  key: 'textState', // 고유한 ID
  default: '', // 초기값
});

2. Selectors

  • Atom의 상태를 기반으로 파생된 상태를 만들거나, 상태를 변형하는 데 사용된다.
  • Selector는 순수 함수로, 다른 Atom이나 Selector를 입력으로 받아 새로운 상태를 반환한다.
  • Selector는 캐싱되어, 동일한 입력에 대해 효율적으로 동작한다.
import React from 'react';
import { selector } from 'recoil';

const charCountState = selector({
  key: 'charCountState', // 고유한 ID
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

3. RecoilRoot

  • Recoil 상태를 제공하는 컨텍스트 컴포넌트로, 애플리케이션 루트에서 사용된다.
  • Recoil 상태를 사용하려면 컴포넌트 트리를 RecoilRoot로 감싸야 한다.
import React from "react";
import { RecoilRoot } from "recoil";
import CharacterCounter from "./CharacterCounter";

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

4. useRecoilState

useRecoilState 훅을 통해 atom의 값을 읽고 업데이트 할 수 있다.

import React from 'react';
import { useRecoilState } from 'recoil';

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

5. useRecoilValue

selector을 사용하는 컴포넌트에서는 useRecoilValue 훅을 사용하여 selector 값을 읽을 수 있다.

import React from 'react';
import { useRecoilValue } from 'recoil';

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

6. 전체 코드

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

// Atom 정의
const textState = atom({
  key: 'textState', // 고유한 ID
  default: '', // 기본값
});

// Selector 정의
const charCountState = selector({
  key: 'charCountState', // 고유한 ID
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}

function CharacterCount() {
  const count = useRecoilValue(charCountState);

  return <>Character Count: {count}</>;
}

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

export default App;

Recoil의 특징

1. 기존 컴포넌트에서 상태 관리

Recoil은 React의 Context API와 유사한 방식으로 상태를 관리한다.
컴포넌트 내에서 상태를 정의하고 사용할 수 있으며, 다른 컴포넌트에서도 해당 상태를 사용할 수 있다.
=> Redux와 같이 별도의 상태 관리 라이브러리를 사용하지 않아도 된다!

2. 비동기 처리

Promise나 async/await를 사용해 비동기 처리를 할 수 있으며, 상태 변화를 감지하여 자동으로 컴포넌트를 업데이트 한다.

3. DevTools 제공

개발자 도구를 제공하여 상태 변화를 쉽게 추적하고 디버깅할 수 있다.

4. 간단한 API

Recoil의 API는 간단하고 직관적이다. atom, selector, useRecoilState, useRecoilValue 등의 간단한 함수들을 사용하여 상태를 관리할 수 있다.

5. React와의 완벽한 통합

Recoil은 React의 Suspense와 같은 기능을 완벽하게 지원하여, 비동기 데이터 로딩을 자연스럽게 처리할 수 있다.

6. 상태 구성의 복잡성

프로젝트 규모가 커질수록 상태의 구조와 의존성을 관리하는 것이 어려워질 수 있다.

Loadable이란?

Recoil의 loadable은 상태의 비동기 작업을 관리하고, 그 상태의 로딩, 성공 및 에러 상태를 쉽게 처리할 수 있도록 도와주는 유틸리티이다.

loadable은 주로 비동기 셀렉터와 함께 사용되어, 비동기 작업의 현재 상태를 확인하고 적절한 UI를 렌더링하는 데 유용하다.

주요 개념

loadable은 Recoil의 비동기 셀렉터로부터 반환된 상태를 감싸고, 다음 세 가지 상태를 제공한다.

  • loading: 비동기 작업이 아직 완료되지 않은 상태.
  • hasValue: 비동기 작업이 성공적으로 완료되어 값을 가진 상태.
  • hasError: 비동기 작업이 실패하여 에러를 가진 상태.

Loadable 예시

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

// 사용자 데이터를 비동기로 가져오는 비동기 셀렉터 정의
const userIdState = atom({
  key: 'userIdState',
  default: 1,
});

const userState = selector({
  key: 'userState',
  get: async ({ get }) => {
    const userId = get(userIdState);
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const user = await response.json();
    return user;
  },
});

function UserInfo() {
  const userLoadable = useRecoilValueLoadable(userState);

  switch (userLoadable.state) { // userLoadable.state를 사용하여 로딩, 성공 및 에러 상태를 처리한다.
    case 'loading': // loading 상태일 때는 "Loading..." 메시지를 표시한다.
      return <div>Loading...</div>;
    case 'hasValue': // hasValue 상태일 때는 사용자 정보를 표시한다.
      return (
        <div>
          <h1>{userLoadable.contents.name}</h1>
          <p>{userLoadable.contents.email}</p>
        </div>
      );
    case 'hasError': // hasError 상태일 때는 에러 메시지를 표시한다.
      return <div>Error: {userLoadable.contents.message}</div>;
    default:
      return null;
  }
}

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

export default App;

useRecoilValueLoadable

Recoil 라이브러리에서 제공하는 훅으로, 주어진 Recoil 상태(Atom 또는 Selector)의 현재 값을 읽어오고, 그 상태가 비동기적인 경우 로딩 상태, 성공 상태, 에러 상태를 쉽게 처리할 수 있도록 도와준다.

이 훅은 주어진 상태의 값을 loadable 객체로 반환하며, 이 객체는 상태가 loading, hasValue, hasError 중 어느 상태인지 확인할 수 있는 방법을 제공한다.
=> 비동기 작업을 간편하게 처리하고, UI를 상태에 맞게 렌더링할 수 있다.

Loadable 장점

- 간편한 상태 관리

loadable을 사용하면 비동기 작업의 다양한 상태를 쉽게 관리할 수 있다.

- 명확한 로직

로딩, 성공, 에러 상태에 따라 분기 처리를 명확하게 할 수 있어, 비동기 작업을 처리하는 로직이 직관적이다.

- React와의 자연스러운 통합

React의 렌더링 흐름과 자연스럽게 통합되어, 비동기 작업의 상태에 따라 적절한 UI를 쉽게 구현할 수 있다.

Recoil 비동기 처리 방법

1. selector와 useRecoilValueLoadable을 사용

Selector 정의

Selector는 일반적으로 비동기 데이터를 가져오는 데 사용된다.

import { selector } from 'recoil';

const userState = selector({
  key: 'userState',
  get: async ({ get }) => {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      const user = await response.json();
      return user;
    } catch (error) {
      throw error;
    }
  },
});

useRecoilValueLoadable 사용

비동기 데이터의 상태를 확인하고, 로딩 상태, 데이터가 있을 때의 처리, 에러 상태 등을 쉽게 관리할 수 있다.

import React from 'react';
import { useRecoilValueLoadable } from 'recoil';
import { userState } from './atoms'; // Recoil에서 정의한 selector 또는 atom 가져오기

function UserInfo() {
  const userLoadable = useRecoilValueLoadable(userState);

  switch (userLoadable.state) {
    case 'loading':
      return <div>Loading...</div>;
    case 'hasValue':
      return (
        <div>
          <h1>{userLoadable.contents.name}</h1>
          <p>{userLoadable.contents.email}</p>
        </div>
      );
    case 'hasError':
      return <div>Error: {userLoadable.contents.message}</div>;
    default:
      return null;
  }
}

export default UserInfo;

2. useRecoilCallback 훅 사용(비동기 데이터 업데이트 및 관리)

useRecoilCallback 훅을 사용하면 Recoil 상태를 업데이트하는 비동기 함수를 생성할 수 있다. 이 방법은 상태 업데이트가 필요한 비동기 작업에 유용하다.

import { atom, useRecoilState, useRecoilCallback } from 'recoil';
import React from 'react';

const userIdState = atom({
  key: 'userIdState',
  default: 1,
});

const userState = atom({
  key: 'userState',
  default: null,
});

function UserInfo() {
  const [userId, setUserId] = useRecoilState(userIdState);
  const [user, setUser] = useRecoilState(userState);

  const fetchUser = useRecoilCallback(({ snapshot, set }) => async () => {
    const id = await snapshot.getPromise(userIdState);
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
    const userData = await response.json();
    set(userState, userData);
  });

  React.useEffect(() => {
    fetchUser();
  }, [userId]);

  return (
    <div>
      {user ? (
        <div>
          <h1>{user.name}</h1>
          <p>{user.email}</p>
        </div>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
}

export default UserInfo;

=> Recoil에서 비동기 데이터를 받아올 때, selector와 useRecoilValueLoadable을 주로 활용하여 상태를 관리한다. selector는 비동기 데이터를 가져오는 역할을 하고, useRecoilValueLoadable은 해당 데이터의 상태를 관리하며 UI를 처리하는 데 사용된다.
useRecoilCallback을 통해 비동기 작업을 수행하고 Recoil 상태를 업데이트하는 경우도 있다.

Recoil 로딩, 성공, 에러 처리는 어떻게 할까?

Recoil에서 로딩, 성공, 에러와 관련된 처리를 useRecoilValueLoadable 훅을 사용하여 처리할 수 있다. useRecoilValueLoadable은 loadable 객체를 반환하며, 이 객체는 비동기 작업의 현재 상태를 나타낸다.

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

// 사용자 ID를 저장하는 Atom 정의
const userIdState = atom({
  key: 'userIdState', // 고유한 키
  default: 1, // 기본값
});

// 사용자 데이터를 비동기로 가져오는 Selector 정의
const userState = selector({
  key: 'userState',
  get: async ({ get }) => {
    const userId = get(userIdState);
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const user = await response.json();
    return user;
  },
});

function UserInfo() {
  // useRecoilValueLoadable 훅을 사용하여 비동기 상태를 관리
  const userLoadable = useRecoilValueLoadable(userState);

  // 상태에 따라 UI를 렌더링
  switch (userLoadable.state) {
    case 'loading':
      return <div>Loading...</div>;
    case 'hasValue':
      return (
        <div>
          <h1>{userLoadable.contents.name}</h1>
          <p>{userLoadable.contents.email}</p>
        </div>
      );
    case 'hasError':
      return <div>Error: {userLoadable.contents.message}</div>;
    default:
      return null;
  }
}

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

export default App;

Recoil과 Redux의 차이

Redux

Redux는 JavaScript 애플리케이션의 상태를 예측 가능하게 관리하기 위한 상태 관리 라이브러리이다.
단일 스토어와 불변 객체 패턴을 사용하여 상태를 관리하며, 상태 변경은 액션(action)을 통해 이루어진다. Redux의 핵심 개념에는 Store, Action, Reducer, Middleware 등이 포함된다.

장점

  • 예측 가능한 상태 관리: 상태 변화를 엄격하게 관리하여 예측 가능한 애플리케이션 상태를 유지할 수 있다.
  • 개발자 도구 지원: Redux DevTools와 같은 강력한 개발 도구를 제공하여 애플리케이션의 상태 변화를 디버깅하고 시각화할 수 있다.
  • 중앙 집중화된 데이터 흐름: 단일 스토어와 불변성을 유지하면서 데이터 흐름을 관리하여 복잡성을 줄일 수 있다.

단점

  • 상태 관리의 복잡성: 처음에는 Redux의 개념과 패턴을 이해하는 데 시간이 걸릴 수 있으며, 상태 관리 로직의 복잡성이 증가할 수 있다.
  • Boilerplate 코드: Action, Reducer, Middleware 등의 추가적인 코드 작성이 필요하며, 큰 프로젝트에서는 Boilerplate 코드가 증가할 수 다.

Recoil

Recoil은 Facebook에서 개발한 React 상태 관리 라이브러리로, React 컴포넌트의 상태를 효율적으로 관리하기 위한 목적으로 설계되었다.
Recoil은 원자(atom) 단위의 상태를 중심으로 구성되며, 전역 상태를 간편하게 공유하고 관리할 수 있는 API를 제공한다.

장점

  • 원자 단위의 상태 관리: 상태를 원자 단위로 분할하고, 컴포넌트 간에 필요한 상태만 공유할 수 있어서 간편하고 유연한 상태 관리가 가능하다.
  • 성능 최적화: React의 컴포넌트 기반 렌더링과 통합되어 효율적인 상태 업데이트와 렌더링 최적화를 제공한다.
  • 비동기 상태 관리: 비동기 데이터를 간편하게 관리할 수 있는 API를 제공하여 복잡성을 줄일 수 있다.

단점

  • 상대적으로 새로운 라이브러리: Redux와 비교하여 커뮤니티 지원이나 사용 사례가 상대적으로 적을 수 있다.
  • 설정 및 초기 학습 비용: 처음에는 Recoil의 개념과 사용법을 익히는 데 시간이 걸릴 수 있으며, 추가적인 학습이 필요할 수 있다.

정리

Redux: 복잡한 상태 관리가 필요하고, 예측 가능한 데이터 흐름을 중요시하는 경우에 적합하다. 큰 규모의 애플리케이션에서 특히 유용하며, 개발자 도구 지원이 강력한 점도 장점이다.
Recoil: React와 통합이 잘 되어 있고, 원자 단위의 상태 관리와 비동기 데이터 처리를 간편하게 처리할 수 있는 경우에 유용하다. 상대적으로 최신 기술이지만, 간편한 사용법과 성능 최적화를 제공한다는 점이 큰 장점이다.

profile
개발과 지식의 성장을 즐기는 개발자

0개의 댓글