Develop/React.js

[React] Recoil 로 React 스럽게 상태관리하는법

dawonny 2023. 5. 20. 01:12
728x90
반응형

상태관리 라이브러리 없이 프로젝트를 하다가 아, 이건 안되겠다. 전역으로 상태관리해야겠다! 싶은 부분이 있었다.

근데 오늘 아침 다른 개발자분 티스토리 피드를 보다가 Recoil 을 추천하시는 글을 읽고

이 참에 Recoil을 써보자 생각했다.

 

설치

먼저 설치한다

npm install recoil

Recoil을 사용하기 위해서는 애플리케이션의 최상위 컴포넌트에 <RecoilRoot> 컴포넌트를 사용해야한다.

index.js 의 파일을 다음과 같이 수정해준다.

index.js

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import App from './App';

ReactDOM.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>,
  document.getElementById('root')
);

그리고 상태를 정의한다.

상태 정의는 atom 이라는 함수를 사용해서 할 수 있다.

나는 recoilState.js 라는 파일을 만들어서 아래와 같이 작성해주었다.

key는 상태를 식별하는 고유한 키이고,

default 는 초기값이다.

Atom

// recoilState.js
import { atom } from 'recoil';

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

특정 컴포넌트에서 Recoil의 상태를 사용하려면

useRecoilState 라는 훅을 사용해서 상태값을 가져오고, 상태값 변화 또한 가능하다.

useRecoilState

import React from 'react';
import { useRecoilState } from 'recoil';
import { countState } from './recoilState'; // Recoil 상태를 가져옴

function Counter() {
  const [count, setCount] = useRecoilState(countState);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

 

일단 Redux처럼 엄청난 보일러 플레이팅을 안해도 된다는 점에서 너무 매력적이다. 

 

useRecoilValue 와 useSetRecoilValue

이 둘은 Recoil 의 두가지 변형이다.

useRecoilState 는 상태의 값을 가져오고, 설정했다면

useRecoilValue 는 상태의 값을 가져오기 에만 사용되고

useSetRevoilState 는 상태의 값을 설정하는 데만 사용된다.

 

위에서 보았던 코드에 적용하면 다음과 같을 수 있다.

import React from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { countState } from './recoilState'; // Recoil 상태를 가져옴

function Counter() {
  const count = useRecoilValue(countState); // 상태의 값을 가져옴
  const setCount = useSetRecoilState(countState); // 상태를 설정하는 함수

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

useResetRecoilState()

useResetRecoilState() 는 말 그래도 인자로 받아온 atom의 state 를 default 값으로 reset 시킨다.

사용은 아래와 같이 한다.

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

// 기본 상태(atom)
const countState = atom({
  key: 'countState',
  default: 0,
});

function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const resetCount = useResetRecoilState(countState); // 상태 초기화

	...

selector + get

Recoil 의 selector 는 다른 state 를 파생할 수 있다. (state의 변환, 조합, 필터링 등을 수행할 수 있다.)

아래는 selector의 예시이다.

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

// 기본 상태(atom)
const countState = atom({
  key: 'countState',
  default: 0,
});

// 파생 상태(selector)
const evenOrOddState = selector({
  key: 'evenOrOddState',
  get: ({ get }) => {
    const count = get(countState);
    return count % 2 === 0 ? '짝수' : '홀수';
  },
});

function Counter() {
  const [count, setCount] = useRecoilState(countState);
  const evenOrOdd = useRecoilValue(evenOrOddState); // 파생 상태를 가져옴

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <p>Even or Odd: {evenOrOdd}</p> {/* 파생 상태 출력 */}
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

위의 예시에서 evenOrOddState 는 countState 를 짝수 또는 홀수로 구분해서 문자열을 반환한다.

get 함수를 통해서 countState 값을 가져와야한다.

 

그리고 이 evenOrOddState 를 사용할 때 useRecoilValue 를 사용해서 countState 의 파생상태를 가져오게 된다.

selector 는 read-only 라고 할 수 있다.

 

이렇게 작성한 evenOrOddState 는 write 가 불가능하다.

즉 useSetRecoilState 나 useRecoilState 의 파라미터로는 넣을 수 없다.

하지만 selector 에 set 속성을 추가하면 write 가 가능해진다.

 

selector + get + set

위에선 selector 안에 get 만 존재했는데, set 도 작성할 수 있다.

set 속성은 해당 selector 가 read-write 상태를 가지게 한다.

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

const nameState = atom({
  key: 'nameState',
  default: 'John',
});

const greetingsState = selector({
  key: 'greetingsState',
  get: ({ get }) => {
    const name = get(nameState);
    return `Hello, ${name}!`;
  },
  set: ({ set }, newValue) => {
    const newName = newValue.replace('Hello, ', '').replace('!', '');
    set(nameState, newName);
  },
});

function MyComponent() {
  const [greetings, setGreetings] = useRecoilState(greetingsState);

  const handleChangeGreetings = () => {
    setGreetings('Hello, Alice!'); // `set` 함수를 사용하여 greetingsState 업데이트
  };

  return (
    <div>
      <p>Greetings: {greetings}</p>
      <button onClick={handleChangeGreetings}>Change Greetings</button>
    </div>
  );
}

export default MyComponent;

이 예시에서 greetingState 는 nameState에 의존해서 파생state 를 계산하는 selector 이다.

여기서 setGreetings 함수는 selector 의 set 속성을 통해서 생성되는 함수이다.

set 함수는 업데이트할 새로운 값을 인자로 받아서 nameState 를 업데이트 해준다.

 

 

selector + 비동기처리 + useRecoilValueLoadable

이 selector의 역할을 생각하면, 원래 사용하던 방식을 조금 고칠 수 있다.

만약에 api 통신과 같은 비동기 처리를 평소에 어떻게 했었는지 생각해보자.

나는 component에서 api 통신을 하고, 불러온 데이터를 atom에 저장했었다.

하지만 이렇게 atom 상태가 변할때마다 각 컴포넌트에서 비동기 처리를 따로 해주면 atom을 구독하고 있던 컴포넌트들은

알아서 리렌더링된다.

selector 는 기본적으로 캐싱 기능이 있어 이미 들어왔던 적이 있는 값을 기억해서, 같은 응답을 보내는 api 호출은 추가적으로 요청하지 않는다. 따라서 성능상 유리하다.

 

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

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

const dataSelector = selector({
  key: 'dataSelector',
  get: async ({ get }) => {
    try {
      // 비동기 API 호출을 수행
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      return data;
    } catch (error) {
      throw new Error('Failed to fetch data');
    }
  },
});

function MyComponent() {
  const dataLoadable = useRecoilValueLoadable(dataSelector);

  switch (dataLoadable.state) {
    case 'loading':
      return <div>Loading...</div>;
    case 'hasValue':
      const data = dataLoadable.contents;
      return (
        <div>
          <p>Data: {data}</p>
        </div>
      );
    case 'hasError':
      return <div>Error: {dataLoadable.contents.message}</div>;
    default:
      return null;
  }
}

export default MyComponent;

dateState 라는 atom 이 있고, dataSelector 는 비동기 API 호출을 수행하는 selector 이다.

dataSelector 의 get 함수는 비동기로 데이터를 가져온다.

 

useRecoilValueLoadable 훅으로 dataSelector 를 구독하고, 데이터 상태를 가져온다.

dataLoabable.state 를 사용하면 데이터 상태를 확인할 수 있다.

 

Loadable 객체는 state 와 contents 라는 프로퍼티가있다.

state : hasValue, hasError, loading
contents : hasValue 일 경우 value, hasError 일 경우 Error 객체, loading 일 경우 Promise

 

 

 

selectorFamily(동적 url 의 api 호출 할 때!)

외부에서 파라미터로 값을 받아와 selector에 적용해야할 때, selectorFamily를 이용할 수 있다.

예시는 아래 블로그 juno7803님의 코드를 참고했다.

export const githubRepo = selectorFamily({
  key: "github/get",
  get: (githubId) => async () => {
    if (!githubId) return "";

    const { data } = await axios.get(
      `https://api.github.com/repos/${githubId}`
    );
    return data;
  },
});

api 호출을 할때 url parameter 를 selectorFamily 를 통해 받아와보자

import { useRecoilValue } from 'recoil';
import { selectorFamily } from '../../state';
const Github = () => {
  const githubId = 'Dawon00';
  const githubRepos = useRecoilValue(githubRepo(githubId));
  
  return(
    <>
      <div>Repos : {githubRepos}</div>
    </>
  )

}
export default Github;

useRecoilValue 를 사용하고, 그 안에 selectorFamily의 이름을 적어주면 된다.

 

 

마치며

redux 에 대한 보일러 플레이트도 적고, 러닝커브가 적다는 것을 몸소 느꼈다.

그리고 정말 React 스럽다고 느꼈다. useState 를 쓰는 방법과 비슷하니 적응하기 쉬웠다.

 

ChatGPT 에게 Recoil 의 장점을 물어보며, 포스팅을 마무리해보려한다.


 

ref : https://medium.com/humanscape-tech/recoil-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-285b29135d8e

 

Recoil 알아보기

Facebook에서 만든 React 상태 관리 라이브러리 Recoil에 대해 알아보기

medium.com

https://recoiljs.org/docs/introduction/getting-started/

 

Getting Started | Recoil

Create React App

recoiljs.org

https://velog.io/@juno7803/Recoil-Recoil-200-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Recoil] Recoil 200% 활용하기

아무리 구글링해도 Recoil 기본 예제밖에 나오지 않아 직접 작성한 Recoil 200% 활용법 🙃

velog.io

 

728x90
반응형