Recoil 맛보기: atom, selector

Benza·2022년 10월 24일
2

🔎

목록 보기
9/11
post-thumbnail

Recoil을 사용해야하는 이유

React에서 발생하는 문제들

  1. props Drilling: 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
  2. useContext의 한계: Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.

    이 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게 한다.

Recoil의 특징

  1. 공유상태(shared state)도 React의 로컬 상태(local state)처럼 간단한 get/set 인터페이스로 사용할 수 있다.
  2. 동시성 모드(Concurrent Mode)를 비롯한 다른 새로운 React의 기능들과의 호환 가능성도 갖는다.
  3. 상태 정의는 점진적이고(incremental) 분산되어 있기 때문에, 코드 분할이 가능하다.
  4. 상태를 사용하는 컴포넌트를 수정하지 않고도 상태를 파생된 데이터로 대체할 수 있다. ?
  5. 파생된 데이터를 사용하는 컴포넌트를 수정하지 않고도 파생된 데이터는 동기식과 비동기식 간에 이동할 수 있다.?
  6. 탐색을 일급 개념으로 취급할 수 있고 심지어 링크에서 상태 전환을 인코딩할 수도 있다. ?
  7. 전체 애플리케이션 상태를 하위 호환되는 방식으로 유지하기가 쉬우므로, 유지된 상태는 애플리케이션 변경에도 살아남을 수 있다.

Recoil

Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있다. Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다. Selectors는 atoms 상태값을 동기 또는 비동기 방식을 통해 변환한다.

atoms

상태의 단위, 업데이트와 구독이 가능
atom이 업데이트 되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
atoms은 런타임에서 생성될 수도 있다. atoms는 로컬 컴포넌트 상태 대신 사용할 수 있다.
동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.

atoms 선언

import { atom } from 'recoil';

export default atom<string | undefined>({
  key: 'QuizDifficulty',
  default: undefined,
});

atom function: { key: [unique string], default: [initial value] }
객체를 parameter로 받는 함수

Component에서 활용

  1. useRecoilState hook import
import { useRecoilState } from 'recoil';
  1. useRecoilState hook을 실행하여 state값과 setState()를 return 받는다.
const [quizDifficulty, setQuizDifficulty] = useRecoilState(
    QuizDifficultyState
  );

useState()와 비슷하지만 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있다.

Selector

Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수함수이다.

상위의 atoms 또는 selectors가 업데이트 되면 하위의 selector함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링 된다.

전역 상태를 관리하기 위한 방법 atom이 하지 못하는 두가지를 할 수 있다.

  1. atom을 구독하는 기능
    이미 선언된 atom의 값이 변할때 그 atom을 구독하고 있다가 Selector가 다시 한 번 실행된다.

  2. server와 통신해서 response 값을 가질 수 있다.

seletor 선언

const initilaOrderState = selector<TResponseData>({
  key: 'initilaOrderState',
  get: async ({ get }) => {
    // QueryDataState atom을 구독
    const queryData = get(QueryDataState);
    if (
      queryData == undefined ||
      window.location.pathname != `/${QUIZ_PAGENAME}`
    )
      return undefined;

    const { amount, difficulty } = queryData;

    const axios = customAxios();
    const response = await axios({
      method: 'GET',
      params: {
        amount,
        difficulty,
        type: 'multiple',
      },
    });
    
    ...
    (decoding logic)
    ...
    
    // 서버로 부터 받아온 response data를 rerurn
    return decodedResponseData;
  },
  set: ({ get, set }) => {
    const amount = get(QuizNumbersState);
    const difficulty = get(QuizDifficultyState);

    set(QueryDataState, { amount, difficulty });
    set(QuizNumbersState, DEFAULT_NUMBERS);
    set(QuizDifficultyState, undefined);
  },
});

get프로퍼티: 구독한 atom의 값이 변경 될 때 재실행 된다.
위의 코드의 경우 QueryDataState이 변경되면 서버로 부터 quiz 데이터를 받아오는 logic을 재실행한다.

set프로퍼티: 자체적으로 setState()를 할 수없기 때문에 비동지적으로 setState()를 해주는 기능을 한다.

get()으로 atom의 값을 가져오고 set()함수를 통해 update한다.

update를 하게되면 selector가 구독하고 있으므로 atom의 변경을 인지하고 get프로퍼티의 기능을 수행한다.

set프로퍼티가 없으면 set을 할 수 없다.

selector 자체는 state 본체라고 할 수 없다. atom의 파편이다. 즉, atom을 무조건 구독해야한다.
만약 구독하지 않더라도 비동기 function이기 때문에 setState()로 새롭게 수정하는 로직을 실행할 수 없다.

useResetRecoilState()

import { useResetRecoilState } from 'recoil';

selector로 선언된 global state를 reset한다.

const resetIntialProps = useResetRecoilState(InitialPropsState);

selector를 rendering 하는 방법

<Suspense fallback={<ShimmerPage />}>
  <Switch>
    <Route path={`/${QUIZ_PAGENAME}`}>
      <Helmet title="Quiz page" />
      <QuizPage /> // 비동기 데이터를 받는 컴포넌트
    </Route>
    ...
  </Switch>
</Suspense>
const initialProps = useRecoilValue(InitialPropsState);

참고

Recoil 공식 문서
YouTube - 가장 쉬운 웹개발 with Boaz

profile
Understanding the impression

0개의 댓글