yehey's 공부 노트 \n ο(=•ω<=)ρ⌒☆

[Next js] recoil 전역 상태관리 적용하기 본문

개발/프로젝트

[Next js] recoil 전역 상태관리 적용하기

yehey 2024. 1. 17. 15:05

배경

프로젝트 진행하면서 전역 상태관리를 사용해야하는 단계가 왔다. 검색을 할 때 조건을 저장해두거나, 입력 폼 값 등을 받아놓을 전역 상태가 필요해서 찾아보았다.

기존에 사용해보았던건 Redux toolkit 이었고 보일러 플레이트 코드가 많다는 단점이 있다. 현재 진행중인 프로젝트는 크기가 크지 않고 복잡한 상태관리는 필요하지 않았기에 Redux 는 사용하지 않기로 했다. Redux를 사용하면 store,reducers, action 등을 모두 작성해야하기 때문에 현재 프로젝트에서는 적합하지 않다고 생각했다. 그치만 장점이라고 볼 수 있는 devtools 와 디버깅을 놓치는 부분은 좀 아쉬웠다.

그 외에 생각한 라이브러리는 Recoil 이다. 무엇보다 사용할 때 코드가 간편하고, useState와 같은 리액트 훅 처럼 코드를 작성할 수 있어서 직관적이었다. 가이드 또한 굉장히 짧고 러닝커브가 크게 있지 않았다. 단점으로는 devtool 이 없다는 점, 정확히 말하면 없진 않지만 안정적이고 쉽게 사용할만한 devtool이 없다. redux의 devtool은 디버깅에 큰 도움이 되지만 recoil은 그냥 냅다 콘솔에 찍기..! snapshot 이라는 개념도 있지만 이 친구도 콘솔에 출력한다. (https://recoiljs.org/ko/docs/guides/dev-tools)

어쨌든 현재 프로젝트 단계에서는 가볍고 쉬운 그리고 문법에 크게 어긋나지 않고 react 처럼 쓸 수 있는 Recoil 을 사용하기로 했다.
(관리해야하는 상태가 별로 없기 때문에~)

Contents

  1. Atom
    Atom은 recoil 에서 상태의 단위로 볼 수 있다. atom()은 쓸 수 있는 (수정가능한) RecoilState를 반환한다.
function atom<T>({
  key: string,
  default: T | Promise<T> | RecoilValue<T>,

  effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,

  dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
  • key - 내부적으로 atom을 식별하는데 사용되는 고유한 문자열. 이 문자열은 어플리케이션 전체에서 다른 atom과 selector에 대해 고유해야 한다.
  • default - atom의 초깃값 또는 Promise 또는 동일한 타입의 값을 나타내는 다른 atom이나 selector.
  • effects_UNSTABLE - atom을 위한 선택적인 Atom Effects 배열.
  • dangerouslyAllowMutability - Recoil은 atom을 이용해 다시 렌더링 되는 컴포넌트에 언제 알려야 할지 알기 위해 atom의 상태 변화에 의존한다. 만약 atom의 값이 변경될 경우, 이를 거치지 않고 등록된 컴포넌트에 제대로 알리지 않고 상태가 변경될 수 있다. 이를 방지하기 위해 저장된 모든 값이 변경되지 않는다. 경우에 따라 이 옵션을 사용하여 이 옵션을 재정의할 수 있다.
  1. Selector
    Selector는 Recoil에서 함수나 파생된 상태를 나타낸다. 주어진 종속성 값 집합에 대해 항상 동일한 값을 반환하는 부작용이 없는 "순수함수"라고 생각하면 된다. get 함수만 사용하면 Selector는 읽기만 가능한 RecoilValueReadOnly 객체를 반환한다. set 함수도 사용하면 Selector는 읽기,쓰기 가능한 RecoilState 객체를 반환한다.
    Selector 를 사용할 때 get 함수는 필수, set 함수는 선택이라고 볼 수 있다.
function selector<T>({
  key: string,

  get: ({
    get: GetRecoilValue
  }) => T | Promise<T> | RecoilValue<T>,

  set?: (
    {
      get: GetRecoilValue,
      set: SetRecoilState,
      reset: ResetRecoilState,
    },
    newValue: T | DefaultValue,
  ) => void,

  dangerouslyAllowMutability?: boolean,
})
  • key - 내부적으로 atom을 식별하는데 사용되는 고유한 문자열. 이 문자열은 어플리케이션 전체에서 다른 atom과 selector에 대해 고유해야 한다. 지속성을 위하여 사용된다면 실행 전반에 걸쳐 안정적일 필요가 있다.
  • get - 파생된 상태의 값을 평가하는 함수. 값을 직접 반환하거나 비동기적인 Promise나 또는 같은 유형을 나타내는 다른 atom이나 selector를 반환할 수 있다. 첫 번째 매개변수로 다음 속성을 포함하는 객체를 전달한다:
    • get - 다른 atom이나 selector로부터 값을 찾는데 사용되는 함수. 이 함수에 전달된 모든 atom과 selector는 암시적으로 selector에 대한 의존성 목록에 추가된다. Selector의 의존성이 변경되면 Selector가 다시 평가된다.
  • set? (optional) - 이 속성이 설정되면 selector는 쓰기 가능한 상태를 반환한다. 첫번째 매개변수로 콜백 객체와 새로 입력 값이 전달된다. 사용자가 selector를 재설정할 경우 새로 입력 값은 T 타입의 값 또는 DefaultValue 타입의 객체일 수 있다. 콜백에는 다음이 포함된다.:
    • get - 다른 atom이나 selector로부터 값을 찾는데 사용되는 함수. 이 함수는 selector를 주어진 atom이나 selector를 구독하지 않는다.
    • set - 업스트림 Recoil 상태의 값을 설정할 때 사용되는 함수. 첫 번째 매개변수는 Recoil 상태, 두 번째 매개변수는 새로운 값이다. 새로운 값은 업데이트 함수나 재설정 액션을 전파하는 DefalutValue 객체일 수 있다.
  • dangerouslyAllowMutability - Selector는 파생된 상태의 "순수 함수"를 나타내며 항상 동일한 의존성 입력 값 집합에 대하여 동일한 값을 반환해야 한다. 이를 보호하기 위해 selector에 저장된 모든 값은 기본적으로 고정되어 있다. 경우에 따라 이 옵션을 사용하여 재정의해야 할 수 있다.
  1. Recoil State Hook
  • useRecoilState(): atom을 읽고 쓰려고 할 때 이 Hook을 사용한다. 이 Hook는 atom에 컴포넌트을 등록하도록 한다.
  • useRecoilValue(): atom을 읽기만 할 때 이 Hook를 사용한다. 이 Hook는 atom에 컴포넌트를 등록하도록 한다.
  • useSetRecoilState(): atom에 쓰려고만 할 때 이 Hook를 사용한다.
  • useResetRecoilState(): atom을 초깃값으로 초기화할 때 이 Hook을 사용한다.

Practice

검색에 사용할 전역 상태 추가 (시작일, 종료일, 타입)

// 전역 상태 타입
export const DefaultStatisticFilterType={
  start: string;
  end: string;
  archiveTypeID: number;
}

// 기본 값 설정
export const defaultStaticFilterInitialValue: DefaultStatisticFilterType = {
  start: formatDate(getFirstDay(new Date())),
  end: formatDate(getLastDay(new Date())),
  archiveTypeID: 0,
};

// atom 생성
export const defaultStatisticFilter = atom<DefaultStatisticFilterType>({
  key: 'defaultFilter',
  default: defaultStaticFilterInitialValue,
});

 

위와 같이 Recoil 전역 상태를 추가할 수 있다.
이번에는 전역 상태 defaultStatisticFilter  archiveTypeID를 기반으로 현재 테마 색을 반환하도록 Selecor 를 추가해보자

const getValidKey = (key: string) => {
  if (!Object.keys(ColorKeyType).includes(key)) {
    return 'green';
  }
  return ColorKeyType[key];
};

export const currentArchiveTypeColor = selector({
  key: 'currentColor',
  get: ({ get }) => getValidKey(get(defaultStatisticFilter).archiveTypeID.toString()),
});

 

archiveTypeID 값을 가져와서 string 처리 후 ColorKeyType 에서 해당하는 값을 반환하도록 하는 Selector 를 추가했다.

이번에는 전역 상태를 컴포넌트에서 사용해보자

//value, setter
const [filter, setFilter] = useRecoilState(defaultStatisticFilter);
//only value
const filter = useRecoilValue(defaultStatisticFilter);
//only setter
const setIsLoggedIn = useSetRecoilState(defaultStatisticFilter);

//selector
const color = useRecoilValue(currentArchiveTypeColor);

Learned

전역 상태를 확인하고 디버깅하는 과정은 좀 불편하지만 코드로 봤을 때는 redux와 비교가 정말 많이 될 만큼 간단하고 좋았다. 현재는 간단한 상태관리에만 사용하고 있지만, selector를 더 세분화해서 적절하게 사용하는 것도 좋을 것 같다.
추후에는 필요한 경우 recoil-persist 를 추가해서 사용해 볼 계획이다. recoil-persist 는 storage에 값을 저장하기 때문에 민감한 정보는 넣을 수 없지만 새로고침해도 남아있어야하는 데이터를 넣을 때는 좋을 것 같다.

참조

https://recoiljs.org/ko/docs/introduction/installation

 

설치 | Recoil

NPM

recoiljs.org

https://recoiljs.org/ko/docs/api-reference/core/atom

 

atom(options) | Recoil

atom은 Recoil의 상태를 표현한다. atom() 함수는 쓰기 가능한 RecoilState 객체를 반환한다.

recoiljs.org

 

Comments