Recoil

은채·2022년 9월 27일
0
post-thumbnail

Recoil vs Redux

Redux

  • 액션, 리듀서, 미들웨어 등 boilerplate 코드가 많이 발생함
  • Redux 의 상태 구조는 트리 구조를 따름
  • Redux 에서는 reselect 같은 3rd-party 라이브러리가 필요

Recoil

  • Recoil 은 boilerplate-free API 를 제공함.
  • React 의 useState 처럼 간단한 게터(get) / 세터(set) 인터페이스로 사용 가능
  • Recoil 은 방향 그래프(directed graph, digraph) 를 따름
  • Recoil 은 상태를 사용하는 컴포넌트를 수정하지 않고 파생 데이터(derived data)를 대체할 수 있음
    • 기본적으로 아톰(atom)의 데이터가 변경되면 해당 atom 을 구독하는 모든 컴포넌트들은 갱신
  • AtomEffect 를 사용해서 특정 상태의 갱신 이후의 사이드 이펙트를 자체적으로 정의 가능
  • 상태 갱신 이후에 영향받는 컴포넌트에서 직접 useEffect를 사용할 필요가 없음

Atom / Selector

Atom

  • recoil 의 상태 단위
  • store에 저장되고 갱신되는 데이터는 모두 Atom을 기반
  • 아톰이 갱신될 때 그 상태를 구독(subscribe) 하고 있는 컴포넌트는 새로운 값으로 리렌더
  • 아톰은 atom() 함수에 key 와 default 을 전달해서 작성

  • 활용 사례 : 최초 로그인 시, accessToken을 atom에 저장하여, 다른 컴포넌트(사이드 바, 마이페이지 등)에서 사용할 수 있었다.

Selector

  • Selector 는 상태를 기반으로 전달된 데이터를 가공할 때 사용
  • selector() 함수에 key 와 getset 를 전달하여 작성
import { selector } from 'recoil';
...

const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) => {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});
  • get 프로퍼티 : 계산에 사용되는 함수
  • 전달된 get 인수를 사용해서 아톰(Atom)이나 다른 셀렉터(Selector)에 접근
  • 접근한 아톰이나 셀렉터가 업데이트 되면 다시 계산
  • fontSizeState 상태를 가져와 폰트 사이즈를 출력하는 순수 함수처럼 동작.
  • 셀렉터는 쓸 수(write)없기 때문에 useRecoilState를 사용하지 않고 useRecoilValue를 사용
import { useRecoilState, useRecoilValue } from 'recoil';


function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);

  return (
    <>
      <div>Current font size: ${fontSizeLabel}</div>

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

  • 활용 사례 : 유저의 accesstoken이 만료된 것을 파악하고, restore하며 새로운 accesstoken을 발급받을 때, 활용했었다.

비동기 데이터 쿼리

Redux

  • redux-thunk, redux-observable, redux-saga 등의 Middleware 사용
  • Middleware : action 을 dispatch 하고 reducer 에서 상태 업데이트를 하기 전 비동기 처리(예: 네트워크 요청, setTimeout)를 하는 중간자 역할
  • Redux Middleware를 위해 추가되는 boilerplate 코드가 많아짐 + 앱의 규모가 커질 수록 복잡도가 늘어나고 코드 양이 더욱 방대해짐

Recoil

  • 동기 / 비동기 함수 모두 selector 에서 처리
  • render() 함수가 동기이기 때문에 promise 가 resolve 되기 전에 렌더링 할 수가 없음
  • 이때 대기중인 데이터를 처리하기 위해 Recoil 은 React Suspense 와 함께 사용
  • 컴포넌트를 Suspense 로 감싸서 대기중인 하위 항목들을 잡아내고 fallback UI 를 대신 렌더링
/ store.js
export const todoIdState = atom({
  key: "todoIdState",
  default: 1
});

export const todoItemQuery = selector({
  key: "todoItemQuery",
  get: async ({ get }) => {
    const id = get(todoIdState);

    const response = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${id}`
    );

    return response.data;
  }
});

// App.js
import { RecoilRoot } from "recoil";
import { Suspense } from "react";

import Container from "./container";

export default function App() {
  return (
      <RecoilRoot>
        <Suspense fallback={() => <p>Loading...</p>}>
          <Container />
        </Suspense>
      </RecoilRoot>
  );
}

// container/index.js
import { todoItemQuery } from "../store";
import { useRecoilValue } from "recoil";

const Container = () => {
  const data = useRecoilValue(todoItemQuery);

  return <div>{data.title}</div>;
};

export default Container;

파라미터에 따라 비동기 데이터 요청

  • 파라미터를 기반으로 쿼리하고 싶을 땐 selectorFamily 를 사용
// store.js
import axios from 'axios';
import { selectorFamily } from 'recoil';

export const todoItemQuery = selectorFamily({
  key: "todoItemQuery",
  get: (id) => async () => {
    const response = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${id}`
    );

    return response.data;
  }
});


// App.js
import { RecoilRoot } from "recoil";
import { Suspense } from "react";

import Container from "./container";

export default function App() {
  return (
      <RecoilRoot>
        <Suspense fallback={<div>Loading...</div>}>
          <Container id={1} />
        </Suspense>
      </RecoilRoot>
  );
}


// container/index.js
import { todoItemQuery } from "../store";
import { useRecoilValue } from "recoil";

const Container = ({ id }) => {
  const data = useRecoilValue(todoItemQuery(id));

  return <div>{data.title}</div>;
};

export default Container;
profile
반반무마니

0개의 댓글