728x90
리액트 상태관리 라이브러리

 

❓상황

러닝 커브가 쉬운 리액트 상태관리인 Recoil을 공부하고 정리하기위함.

 

📖 Recoil 이란?

Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있다.

 

Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다.

 

Selectors는 atoms 상태값을 동기 또는 비동기 방식을 통해 변환한다.

 

사용률은 낮지만, React를 만든 Facebook에서 만든 상태관리 라이브러리이다.(리액트가 사장될때까지 지원이 된다!)

 

🐸 설치

yarn add recoil

 

🧮 정리

RecoilRoot

recoil 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 나타나는 RecoilRoot 가 필요하다.

 

RecoilRoot는 index.js와 같은 Root 컴포넌트가 가장 좋다.

import ReactDOM from 'react-dom/client';
import { RecoilRoot } from 'recoil';

import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));

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

 

Atom

Atom은 전역 상태(state)를 만들 수 있다.

또한, Atom은 어떤 컴포넌트에서나 읽고 쓸 수 있다.

 

Atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독한다.

그래서 atom에 어떤 변화가 있으면, atom을 구독하는 모든 컴포넌트들이 rerender 된다.

# atom으로 전역 state 만들기
// store.js
import { atom } from 'recoil';

export const fontSizeState = atom({
  key: "fontSizeState", # 고유 키
  default: 14, # 초기값(number, string, object 모두 가능하다.)
});

 

atom을 읽고 쓰게 하기 위해서는 useRecoilState()를 아래와 같이 사용하면 된다.

import { useRecoilState } from "recoil";
import { fontSizeState } from "./store";

export default function FontButton() {
  # value와 setter : useRecoilState(<atom으로 선언한 변수>)
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  
  # value : useRecoilValue(<atom으로 선언한 변수>)
  const fontSize = useRecoilValue(fontSizeState);
  
  # setter : useSetRecoilState(<atom으로 선언한 변수>)
  const setFontSize = useSetRecoilState(fontSizeState);

  return (
    <>
      <button
        onClick={() => setFontSize((size) => size + 1)}
        style={{ fontSize }}
      >
        Click to Enlarge
      </button>
    </>
  );
}

 

React의 useState와 유사하게 사용할 수 있다.(이것 때문에, 배움의 난이도가 확 낮아진다.)

 

Selector

atom으로 전역 상태(state)를 선언하는 것 대신, selector를 이용하여 선언할 수 있다.

 

다만 일반적으로, atom으로 선언한 state의 값을 변형할때 사용한다.

import { atom, selector } from 'recoil';

const fontSizeState = atom({
  key: "fontSizeState", # 고유 키
  default: 14, # 초기값(number, string, object 모두 가능하다.)
});

const fontSizeMutiple = selector({
  key: 'fontSizeMultiple',
  get: ({get}) => {
    return get(fontSizeState) * 2
  }
})

 

selector는 set 프로퍼티를 option으로 가질 수 있다.

set 은 상태값을 변경할때 사용한다.

 

근데, 상태값을 변경하는 코드를 작성할때, 넘어온 값의 인스턴스를 체크해줘야한다.

이유는 reset 함수를 이용하여 상태값을 초기화할 수도 있기때문에, 요청된 것이 상태값을 변경하려는건지 초기화하려는 건지 확인해야한다.

import {atom, selector} from 'recoil';

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

const transformSelector = selector({
  key: 'TransformSelector',
  get: ({get}) => get(myAtom) * 100,
  set: ({set, reset}, newValue) => {
  
    # reset(DefaultValue)에서 넘어왔는지 확인
    if (newValue instanceof DefaultValue) {
      reset(newValue)
      
    # reset이 아니라면, 값을 바꿔준다.
    } else {
      set(mphState, newValue * 2);
    }
  }
    
});

 

useResetRecoilState

상태 값을 초기화하려면, useResetRecoilState 함수를 사용한다.

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

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

const transformSelector = selector({
  key: 'TransformSelector',
  get: ({get}) => get(myAtom) * 100,
  set: ({set, reset}, newValue) => {
  
    # reset(DefaultValue)에서 넘어왔는지 확인
    if (newValue instanceof DefaultValue) {
      reset(newValue)
      
    # reset이 아니라면, 값을 바꿔준다.
    } else {
      set(myAtom, newValue * 2);
    }
  }
    
});

const TodoResetButton = () => {
  const reset = useResetRecoilState(transformSelector);
  
  return <button onClick={reset}>Reset</button>;
};

 

useRecoilState = useRecoilValue + useSetRecoilState

값과 setter, 두개를 사용하고 싶다면, useRecoilState 함수를 사용하고,

값만 사용한다면, useRecoilValue / setter만 사용한다면 useSetRecoilState를 사용하면 불필요한 코드작성을 줄일 수 있다.

 

비동기 State 선언

지금까지는 동기 상태(state)를 선언하는 방법에 대해 기술했다.

다음은 API 통신을 통해, 데이터를 전역 상태의 값으로 가지는 것을 원한다면, selector 함수를 사용한다.

 

import { selector } from 'recoil';

const userNameQuery = selector({
  key: 'userNameQuery',
  get: async ({get}) => {
    const res = await fetch(url);
    const data = res.json();
    return data;
  }
})

 

selector 함수를 이용하여 API 통신값을 저장하는 이유는, 이후에 알아볼 selectorFamily의 매개변수를 이용한 API 요청하여 데이터를 받아올때, 해당 API 값들을 캐싱하여 다시한번더 같은 URL의 API 요청을 하지않도록 성능에 도움을 준다.

 

에러 핸들링

API 통신하여, response의 error를 받아서 에러 핸들링하면 된다.

import { selector } from 'recoil';

const userNameQuery = selector({
  key: 'userNameQuery',
  get: async ({get}) => {
    const res = await fetch(url);
    if (res.error) throw res.error;
    
    const data = res.json();
    return data;
  }
})

 

매개변수를 이용한 비동기 State 선언

매개변수를 이용하지 않고, 비동기 상태를 선언할때 selector를 사용했다.

매개변수를 이용하여, 비동기 상태를 선언할때는 selectorFamily를 사용한다.

import { selectorFamily } from 'recoil';

const userNameQuery = selectorFamily({
  key: 'userNameQuery',
  get: async (userId) => ({get}) => {
    const res = await fetch(`url/${userId}`);
    const data = res.json();
    return data;
  }
})

 

비동기 State 로딩상태 : Suspense

비동기로 API 요청시 기다리는 시간이 있을 것이다.

데이터를 가져오는 시간은 React의 Suspense를 사용하면 로딩상태의 UI를 만들 수 있다.

import { selector, useRecoilValue } from 'recoil';

const userNameQuery = selector({
  key: 'userNameQuery',
  get: async ({get}) => {
    const res = await fetch(url);
    const data = res.json();
    return data;
  }
})

function app() {
  const userName = useRecoilValue(userNameQuery);
  
  return (
    <div>{userName}</div>
  );
}

# index.js
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <App />
      </React.Suspense>
    </RecoilRoot>

 

비동기 State 로딩상태 : useRecoilStateLoadable

React의 Suspense 대신, recoil의 useRecoilStateLoadable을 이용하여 로딩상태의 UI를 만들 수 있다.

 

값을 얻기 위해서 다른 hooks을 사용하지 않아도, useReocilStateLoadable로 선언한 인스턴스의 contens 파라미터가 해당 값을 가지고 있다.

import { selector, useRecoilStateLoadable } from 'recoil';

const userNameQuery = selector({
  key: 'userNameQuery',
  get: async ({get}) => {
    const res = await fetch(url);
    const data = res.json();
    return data;
  }
})

function app() {
  const [userNameLoad, setUserNameLoad] = useRecoilStateLoadable(userNameQuery)
  
  if (userNameLoad.state === 'loading') {
    return <div>Loading...</div>
  }
  
  if (userNameLoad.state === 'hasError') {
    return <div>error...</div>
  }
  
  if (userNameLoad.state === 'hasValue') {
    return <div>{userNameLoad.contents}</div>
  }
}

 

waitForAll

병렬적으로 진행할 수 있도록 도와주는 기능을 가지고 있다.

 

여러개의 전역 상태 가져오기

function FriendsInfo() {
  const [friendA, friendB] = useRecoilValue(
    waitForAll([friendAState, friendBState])
  );
  return (
    <div>
      Friend A Name: {friendA.name}
      Friend B Name: {friendB.name}
    </div>
  );
}

 

비동기 API 통신

쉽게 말하자면, 여러개의 API 통신요청을 해야하는 상황이라면, 여러개를 동시에 API 통신요청하는 것이다.

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(waitForAll(
      friendList.map(friendID => fetch(url + friendID))
    ));
    return friends;
  },
});

 

전역 State 합치기

const friendInfoQuery = ...

const invoicesQuery = ...

const paymentsQuery = ...

const userInfoQuery = selectorFamily({
  key: 'userInfoQuery',
  get: (id) => ({get}) => {
    const {info, invoices, payments} = get(waitForAll({
      info: friendInfoQuery(id),
      invoices: invoicesQuery(id),
      payments: paymentsQuery(id),
    }));

    return {
      name: info.name,
      transactions: [
        ...invoices,
        ...payments,
      ],
    };
  },
});

 

'공부 > 프론트엔드' 카테고리의 다른 글

공부 | Redux Toolkit 정리  (0) 2022.08.12
공부 | swr 정리(ft. react)  (0) 2022.08.04
공부 | next.js 정리  (0) 2022.07.20
공부 | Line Awesome 정리  (0) 2022.07.16
공부 | react-icons 정리  (0) 2022.07.12
복사했습니다!