Recoil를 알아보자

Seonup·2023년 11월 12일
0

본 시리즈는 정재남님의 풀스택 리액트 라이브코딩 - 간단한 쇼핑몰 만들기 강의 내용을 기반으로, 추가적인 학습을 통해 습득한 지식 또는 강의 코드를 다른 방법으로 구현한 경험을 작성하고 있습니다. 강의 코드(GitHub)를 확인하세요.

Recoil

Recoil은 Facebook에서 만든 React를 위한 상태 관리 라이브러리로, 단방향으로 상태를 전달한다.

API

<RecoilRoot>

  • <RecoilRoot>는 하위 컴포넌트에게 recoil 상태를 사용할 수 있도록 제공하는 컴포넌트이다.
  • <RecoilRoot>는 recoil 상태를 공유하는 모든 컴포넌트들의 조상 컴포넌트여야 한다. -> 프로젝트의 root 컴포넌트에 제공하는 것이 효율적일 수 있다.
  • 여러 개의 root가 같이 존재할 수 있으며 각각의 root는 별개의 상태를 가지지만, <RecoilRoot>가 여러 개 존재할 경우 상태 변경 사항이 다른 root와 공유되는 등 예기치 않은 동작이 발생할 수 있기 때문에 하나의 root만 사용할 것을 권장한다.
    • selector 캐시같은 캐시들은 root 사이에서 공유될 수 있다. 이는 selector 함수를 사용하는 다른 컴포넌트에서 이미 생성된 캐시를 사용할 수 있다는 성능 향상의 장점이 있지만, root를 공유할 수 있다는 예측 불가능성이 있다.
    • root들이 중첩된 구조로 작성되어 있다면 최하위의 root가 최상위의 root를 override 할 수 있다.

<RecoilRoot>의 override 속성

  • 기본값은 true로, override 속성이 true일 경우 해당 root는 새로운 recoil scope를 생성한다.
  • override 속성이 false일 경우
    • 일반적으로 override 속성을 false로 하는 것은 recoil에서 제공하는 디버깅 및 개발 도구가 비활성화되므로 권장되지 않는다.
    • 장점
      • Recoil 개발 도구는 Recoil의 상태 및 액션 로그를 브라우저 콘솔에 표시하는데, 배포 시 보안 이슈가 발생될 수 있다. 이럴 경우 override 속성을 false로 해서 recoil 개발 도구를 비활성화 할 수 있다.
    • 단점
      • 상태 변경 알림 없음: atom 또는 selector의 값이 변경될 때 개발자 도구에서 상태 변경 알림이 표시되지 않는다.
      • 디버깅 모드 제한: 개발자 도구에서 제공하는 상태 디버깅, 스냅샷 등의 기능이 제한된다.
      • 액션 로깅 제한: 개발자 도구에서 제공하는 액션 로깅 기능이 제한된다. (액션 로깅 기능을 사용하면 애플리케이션에서 발생하는 모든 상태 변경 작업이 로깅되므로 문제 해결에 도움이 된다.)
      • 성능 저하: 개발자 도구와 관련된 부가적인 작업이 수행되지 않기 때문에 일부 성능 저하가 발생될 수 있다.

<RecoilRoot>의 구조

export type RecoilRootProps =
  | {
      initializeState?: (mutableSnapshot: MutableSnapshot) => void;
      override?: true;
      children: React.ReactNode;
    }
  | {
      override: false;
      children: React.ReactNode;
    };

/**
 * Root component for managing Recoil state.  Most Recoil hooks should be
 * called from a component nested in a <RecoilRoot>
 */
export const RecoilRoot: React.FC<RecoilRootProps>;

atom

  • 하나의 상태를 의미하는 atom은 전역 상태로, 어떤 컴포넌트에서든 참조하고 사용할 수 있다.
  • 컴포넌트가 atom을 참조하는 순간부터 컴포넌트는 atom을 구독하고 있는 것으로, atom 값이 변경되면 atom을 구독하고 있는 모든 컴포넌트는 리렌더링 된다.
  • atom의 반환값 RecoilState이기 때문에 컴포넌트가 atom을 참조할 때는 useRecoilState 메서드를 사용하여 인수에 atom으로 선언된 state를 전달해야 한다.

atom의 구조

interface AtomOptionsWithoutDefault<T> {
  key: NodeKey;
  effects?: ReadonlyArray<AtomEffect<T>>;
  effects_UNSTABLE?: ReadonlyArray<AtomEffect<T>>;
  dangerouslyAllowMutability?: boolean;
}
interface AtomOptionsWithDefault<T> extends AtomOptionsWithoutDefault<T> {
  default: RecoilValue<T> | Promise<T> | Loadable<T> | WrappedValue<T> | T;
}
export type AtomOptions<T> =
  | AtomOptionsWithoutDefault<T>
  | AtomOptionsWithDefault<T>;

/** 기본 atom: RecoilState */
export function atom<T>(options: AtomOptions<T>): RecoilState<T>;

/** 단순히 값을 감싸는데 사용되는 atom: WrappedValue */
export namespace atom {
  function value<T>(value: T): WrappedValue<T>;
}

작성 방법

import { atom, useRecoilState } from "recoil";

const atomState = atom<defaultValueType>({
  key: "uniqueKey",
  default: "initValue",
});

const [state, setState] = useRecoilState(atomState);

Selector

  • selector는 Derived state를 계산하는 데 사용되는 메서드로, 다른 atom이나 selector를 읽어들여 새로운 값을 계산하고 이 값을 다른 컴포넌트에서 사용할 수 있도록 해준다.
    • Derived state: 다른 상태(state)들로 계산해서 얻어내는 새로운 상태값으로, props와 의존성이 있는 state이다.
    • 주어진 종속성 값 집합에 대해 항상 동일한 값을 반환하는 순수함수라고 생각하면 된다.
  • selector는 일반적인 상태값이 아닌, 읽기 전용(read-only)로 사용된다.
  • selector 함수가 실행될 때 마다 selector 캐시가 생성되며 이 캐시는 메모리에 보관되어 재사용된다. 이는 selector 함수를 실행할 때마다 새로운 인스턴스를 만들 필요가 없어져 성능 향상에 도움이 된다.
  • return 값이 RecoilValueReadOnly일 때 selector 값을 참조하기 위해서는 useRecoilValue 메서드를 사용해야 한다.

selector의 구조

export interface ReadOnlySelectorOptions<T> {
  key: string;
  get: (opts: {
    get: GetRecoilValue;
    getCallback: GetCallback;
  }) => Promise<T> | RecoilValue<T> | Loadable<T> | WrappedValue<T> | T;
  dangerouslyAllowMutability?: boolean;
  cachePolicy_UNSTABLE?: CachePolicyWithoutEquality;
}

/** 읽기 전용(get)으로 작성되었을 때의 반환값: RecoilValueReadOnly */
export function selector<T>(
  options: ReadOnlySelectorOptions<T>
): RecoilValueReadOnly<T>;

/** 읽기와 쓰기(get, set)가 모두 허용되었을 때의 반환값: RecoilState */
export function selector<T>(
  options: ReadWriteSelectorOptions<T>
): RecoilState<T>;

/** 단순히 값을 감싸는데 사용되는 반환값: WrappedValue */
export namespace selector {
  function value<T>(value: T): WrappedValue<T>;
}

작성 방법

  • selector의 인수로 key 프로퍼티와 get 메세드를 가진 객체를 전달한다.
  • recoil의 get 메서드는 메서드 축약표현을 사용하지 않고 화살표 함수로 작성한다.
  • selector 값을 참조하기 위해서는 useRecoilValue 메서드를 사용한다.
import { selctor, useRecoilValue } from "recoil";

const mySelector = selector({
  key: "mySelector",
  get: ({ get }) => {
    const value1 = get(myAtom1);
    const value2 = get(myAtom2);

    return value1 + value2;
  },
});

export function Component() {
  const derivedValue = useRecoilValue(mySelector);
  return <div>derivedValue</div>;
}

selectorFamily 메서드

  • selectorFamily 메서드는 key 프로퍼티와 get, set 메서드를 가진 객체를 인수로 전달하면 selector를 반환하는 메서드이다.
  • selectorFamily는 결과값이 RecoilState이기 때문에 useRecoilState 메서드를 이용하여 selector의 값을 참조해야 한다.
  • selectorFamily는 인수 작성 타입이 ReadWriteSelectorFamilyOptions으로 get, set 프로퍼티를 작성할 때 (param) => (options) => returnValue 방식으로 작성한다.

selectorFamily의 구조

export interface ReadWriteSelectorFamilyOptions<
  T,
  P extends SerializableParam
> {
  key: string;
  get: (
    param: P
  ) => (opts: {
    get: GetRecoilValue;
    getCallback: GetCallback;
  }) => Promise<T> | Loadable<T> | WrappedValue<T> | RecoilValue<T> | T;
  set: (param: P) => (
    opts: {
      set: SetRecoilState;
      get: GetRecoilValue;
      reset: ResetRecoilState;
    },
    newValue: T | DefaultValue
  ) => void;
  cachePolicy_UNSTABLE?: CachePolicyWithoutEquality;
  dangerouslyAllowMutability?: boolean;
}

export function selectorFamily<T, P extends SerializableParam>(
  options: ReadWriteSelectorFamilyOptions<T, P>
): (param: P) => RecoilState<T>;

작성 방법

import { selectorFamily } from "recoil";

const mySelector = selectorFamily({
  key: "mySelector",
  get: ({ get }) => {
    // ...
  },
  set: ({ get, set }, newValue) => {
    // ...
  },
});

useRecoilState()

function useRecoilState<T>(
  recoilState: RecoilState<T>
): [T, SetterOrUpdater<T>];
  • 전역 상태인 atom에 접근하고 관리하기 위한 메서드 (읽고 쓰기가 가능)
  • atom의 값을 구독하여 업데이트할 수 있는 hook으로, useState와 동일한 방식으로 사용할 수 있다.
  • atom에 컴포넌트를 등록하는 hook이다.
  • 반환값은 useState와 비슷하게 [recoilState, setRecoilState]이다.

useRecoilValue()

function useRecoilValue<T>(recoilValue: RecoilValue<T>): T;
  • setter 함수 없이 atom의 값을 반환한다. 즉, atom을 읽기만 할 때 사용한다.
  • atom에 컴포넌트를 등록하는 hook이다. (atom이 변경되는 것을 구독한다.)

useSetRecoilState()

function useSetRecoilState<T>(recoilState: RecoilState<T>): SetterOrUpdater<T>;
  • setter 함수만 반환한다. 즉, atom 값을 변경하는 등 쓰기만 할 때 사용한다.

useResetRecoilState()

function useResetRecoilState(recoilState: RecoilState<any>): Resetter;
  • atom을 초깃값으로 초기화할 때 사용한다.

useRecoilCallback()

function useRecoilCallback<Args extends ReadonlyArray<unknown>, Return>(
  fn: (interface: CallbackInterface) => (...args: Args) => Return,
  deps?: ReadonlyArray<unknown>
): (...args: Args) => Return;
  • 컴포넌트를 atom에 등록하지 않고 값을 읽어야 하는 경우에 사용한다.
  • 이벤트에 대한 응답으로 Recoil state에 접근하는 데 유용하다.
profile
박선우

0개의 댓글