React Recoil 및 Recoil 비동기 처리

김은호·2023년 1월 11일
3

상태 관리 라이브러리의 필요성

State?

프론트엔드에 동적으로 표현되는 데이터이다. state는 부모 컴포넌트 -> 자식 컴포넌트로 prop으로만 전달이 가능하다.

A -> B -> C -> D의 구조에서 A의 state를 D에서 사용하려면 B, C는 그 state를 사용하지 않더라도 props로 A -> B -> C -> D로 내려줘야 한다.

해결 방법

상태 관리 라이브러리를 이용하면 state 저장소를 전역에 두어 어느 컴포넌트에서도 해당 저장소에 접근할 수 있어 위 문제가 해결된다.

Recoil Basic

$ npm i recoil @types/recoil

Atom

recoil에서 쓰이는 state의 단위 unique한 key값으로 구분된다.

atom이 업데이트가 되면 해당 atom을 구독(참조 / 사용)하는 모든 컴포넌트들의 state가 새로운 값으로 업데이트 되고 리렌더링된다.

import {atom} from "recoil"

interface IPerson {
  name: string;
  age: number;
}

// Person = IPerson의 배열
export const Person = atom<IPerson[]>({
  key: "person",
  default: []
});
  • key: 고윻한 atom을 구분
  • default: atom의 기본 값을 지정 가능

Usage

먼저 사용하려면 최상위 컴포넌트에서 RecoilRoot로 다른 컴포넌트를 감싸줘야한다.

// src/index.tsx

import { createRoot } from "react-dom/client";
import { RecoilRoot } from "recoil";
import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement!);

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

전반적으로, React의 useState Hook과 사용법이 비슷하다.

useRecoilValue

atom의 값만 얻어오는 것, 변경은 못함

const val = useRecoilValue(myAtom);

useSetRecoilState

atom의 값을 변경할 수 있는 set 함수를 얻음

const setVal = useSetRecoilState(myAtom);

useRecoilState

atom의 값도 얻고 변경도 할 수 있는 set 함수도 얻음
useState와 비슷

const [val, setVal] = useRcoilState(myAtom);

selector

recoil은 전역 상태 관리 라이브러리 -> 두 컴포넌트가 구독 중인데 한 컴포넌트의 변경이 다른 컴포넌트에 영향을 줄 수 있다.
-> selector의 등장

파생된 state를 저장하는 Recoil에서 제공하는 순수함수

순수함수: 동일한 값(파라미터)을 제공하면 동일한 값을 Return

내부에서 get 메서드로 atom을 사용할 수 있음 & atom을 가공하여 새로운 값 반환 가능, 이때 기존 atom의 값은 변환시키지 않는다.

set 메서드로 기존 atom 값을 변형시킬 수 있는데, 이 때 selector에 대한 set 함수가 있어야한다.(useRecoilState 등을 이용)

// atom.tsx

import { atom, selector } from "recoil";

export const cmState = atom<number>({
  key: "cm",
  default: 0,
});

export const inchSelector = selector<number>({
  key: "inch",
  get: ({ get }) => {
    const cm = get(cmState);
    return cm / 2.54;
  }, // get을 사용해서 cmState에 2.54를 나눈 값을 출력하게 만들었다.
	set: ({ set }, newValue) => { // selector setter에 넘겨진 파라미터가 newValue로 들어감
    set(cmState, Number(newValue) * 2.54);
  },
});

//-----------------------------------------------------------------------------------------------//

// app.tsx

import { useRecoilState } from "recoil";
import { cmState, inchSelector } from "./atom";

function CmToInch() {
  const [cm, setCm] = useRecoilState(cmState); // atom
  const [inch, setInch] = useRecoilState(inchSelector); // selector
  //selector도 set 함수를 만들어준다.

  const onCmChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = event;
    setCm(+value); // string to number
  };

  const onInchChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
      currentTarget: { value },
    } = event;
    setInch(+value);
  }; //onCmChange와 동일

  return (
    <>
      <input onChange={onCmChange} value={cm} placeholder="CM" />
      <input onChange={onInchChange} value={inch} placeholder="Inch" />
    </>
  );
}
export default CmToInch;

cm의 input에 값을 입력하면 inch에는 inchSelector의 get 함수를 통해 변형된 inch 값이 출력된다.

inch의 input에 값을 입력하면(setInch(value)) selector의 set 함수가 실행되고 atom인 cmState의 값을 바꾼다 -> cm에는 바뀐 cmState값이 출력된다.

Recoil 비동기 처리

비동기 처리: 비동기로 동작하는 코드가 있으면 그 결과값을 받아오기 위해 동기적으로 동작하도록 만들어주는 것

selector을 이용하여 처리 가능

selector을 활용하여 사용자, repository의 정보를 입력하면 star의 개수를 나타내는 프로그램

// atom.ts

export const githubInfoState = atom({ // 사용자 정보, repo를 저장하는 atom
  key: "githubInfomation",
  default: { users: "", repo: "" }
});

export const getStars = selector({
  key: "get/github-repo-stars",
  get: async ({ get }) => {
    const { users, repo } = get(githubInfoState); // atom 정보를 얻어옴
    if (!users || !repo) return;
    const url = `https://api.github.com/repos/${users}/${repo}`;
    try {
      const response = await fetch(url); // 얻어온 atom 정보를 활용하여 url fetch
      const data = await response.json();
      return data?.stargazers_count; // star 개수를 반환
    } catch (err) {                  
      throw Error("잘못된 깃허브 정보입니다!");
    }
  }
});
  
//----------------------------------------------------------------------------------------------//
  
// Star.tsx
  
/* ... */
const Stars = () => {
const userRepoStars = useRecoilValue(getStars); // selector

const stars = userRepoStars ? `${userRepoStars}` : "";
return <StyledStars>{stars}</StyledStars>;
};

//----------------------------------------------------------------------------------------------//

// App.tsx
const App = () => {
    ...
    return (
        ...
        <Suspense fallback={<div>loading...</div>}>
            <Stars />
        </Suspense>
        ...
    )
}
  • Suspense의 fallback property에 로딩중일 때 보여줄 컴포넌트를 넘겨주면된다.

Selector Caching

Selector은 기본적으로 값을 캐싱 -> 이전에 들어왔던 api라면 캐시에서 바로 꺼내서 사용한다

Recoil 단점

서버의 정보가 바뀌었는데 api를 캐싱해서 업데이트가 안될 수도 있다

→ selector은 구독중인(참조중인 atom) or Family의 파라미터 정보가 변경되면 다시 업데이트되므로 그것을 활용하거나 Refresh state를 하나 만들어서 selector가 구독하게 만들고 api 요청이 필요할 때 변경시키도록한다.
또는 selector의 set을 활용해서 atom을 변경시키면 구독중인 정보가 변경되는 것이므로 같은 효과를 얻을 수 있다.

set으로 atom의 state을 변경시키면 여러 selector가 그 atom을 구독중일 때 동시에 여러 selector을 갱신시켜줄 수 있는 장점이 있다.
→ 단, 클라이언트 정보 변경에 한정된다. 서버의 정보 변경은 클라이언트 단에서는 모른다.
→ 시간 정보를 쿼리로 담아 api 요청을 보내도록 해서 매번 새로운 요청을 하도록 하여 캐싱이 안되도록 하는 방법도 존재한다.

가장 좋은 것은 refresh 기능이 recoil에 탑재되는 건데 아직 없다고 한다.

recoil을 활용할 때 atom이 여러 selector에 활용될 수 있으므로 flow chart를 잘 설계해서 어떤 selector가 어떤 atom을 구독하는지 파악하는게 중요하다.

Recoil을 활용한 초기 설계시 고려할 점

  • 꼭 전역으로 관리될 필요 없는 것은 Hooks와 Props로 설계한다.

  • 여러 state 값을 사용 용도에 따라 분류하기 → UI 전용, 폼데이터 전용 등

  • 어떤 데이터를 캐싱하고, 어떤 데이터가 실시간으로 반영되어야 하는지 파악하기

  • 캐싱: 사용자 정보 등, 주기적: 자주 바뀌지 않는 정보, 실시간: 주요 데이터

  • 데이터가 어느 시점에 변경되고 어느 부분에 영향을 주는지 예측하기

Reference

https://tech.osci.kr/2022/09/02/recoil-selector/

useQuery를 통한 state management

query의 캐시에 client state을 저장하고 그것을 꺼내는 메커니즘이라고 한다.

0개의 댓글