zustand 중심의 상태 관리에 관한 이야기

https://www.youtube.com/watch?v=nkXIpGjVxWU

보다가 늘 말로만 듣던 Flux 패턴이라는 게 나왔다.

https://velog.io/@andy0011/Flux-패턴이란

react query의 쿼리 키를 만들 때 변수 작명 같아서 고민일 때가 많은데 그럴 때 쿼리 키 생성 라이브러리 사용 가능

https://tanstack.com/query/v4/docs/framework/react/community/lukemorales-query-key-factory

회사에서 zustand를 사용하고 있는데 써본지 오래되어서 공식 문서를 읽고 다시 한번 따라해보면서 간단히 메모한다.

zustand

create 함수를 사용해서 상태를 생성하고 set 함수를 사용해서 상태를 변경하는 함수를 정의한다. set에는 상태를 업데이트하는 콜백을 넘긴다.

import { create } from "zustand";

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));

이때 객체의 프로퍼티가 많다면 {...state, bears: state.bears + 1 }와 같이 기존 상태를 복사해서 새로운 상태를 반환하는 방식으로 불변성을 유지하면서 상태를 업데이트해야 하지만, 이건 워낙 흔한 패턴이므로 set 이 자동으로 상태를 병합해주기 때문에 굳이 복사할 필요는 없다.

이런 자동 병합을 끌 수도 있는데 그러면 set함수의 2번째 인자인 replacetrue로 설정하면 된다. 이 경우 상태가 완전히 교체된다.

set((state) => newState, true)

단 이런 자동 병합은 얕은 복사로 이루어진다(it will be shallowly merged with the existing state in the store.). 따라서 중첩 객체 업데이트 시 ... 연산자를 사용해서 상태를 복사하여 불변 객체를 보장해줘야 한다. 하지만 깊이 중첩된 객체의 경우 Immer와 같은 대안을 고려할 수 있다. 깊은 중첩 객체를 복사하는 건 실수의 여지도 많기 때문이다.

상태를 가져와 사용할 땐 create에서 리턴하는 use~Store 훅을 사용한다. state에서 필요한 속성을 선택적으로 가져온다.

import { useStore } from "./store";

function App() {
  const bears = useStore((state) => state.bears);
  const increasePopulation = useStore((state) => state.increasePopulation);

  // ...
}

이런 식으로 선택적으로 속성을 가져오는 걸 셀렉터를 이용한다고 한다. 그런데 이런 콜백을 매번 쓰는 게 귀찮다면 자동으로 생성하는 함수를 만들어줄 수 있다고 한다. Auto Generating Selectors

combine 함수를 사용해서 initial state와 업데이트 함수를 한번에 정의할 수도 있다. 이러면 state creator 함수가 리턴되는데 타입 추론도 더 잘된다고 함.

https://zustand.docs.pmnd.rs/middlewares/combine

combine<T, U>(initialState: T, additionalStateCreatorFn: StateCreator<T, [], [], U>): StateCreator<Omit<T, keyof U> & U, [], []>

보통 combine(초기상태, set을 이용해서 상태를 업데이트하는 함수를 담은 객체) 형태로 사용한다.

Flux inspired practice

상태 업데이트 함수는 변경하는 데이터와 같은 store에 두는 게 추천된다.

const useStore = create((set) => ({
  x:0,
  y:0,
  setX: (x: number) => set({ x }),
  setY: (y: number) => set({ y }),
}));

store 훅의 setState 메서드를 이용해서 외부에서 state를 업데이트하는 것도 가능은 하다. Practice with no store actions

위의 훅이라면 이런 식으로 사용할 수 있다. setX를 선언하는 대신 이렇게 직접 setState를 사용해서 상태를 업데이트하는 함수를 만드는 것.

const increaseX = useStore.setState((state) => ({ x: state.x + 1 }));

create vs createStore

그리고 create 함수는 상태를 사용할 수 있게 하는 커스텀 훅을 리턴한다. createStore는 상태 저장소 그 자체를 리턴한다. 즉 훅으로 사용할 게 아닐 경우(조건문, context API 등에서 사용하고 싶다면) createStore를 써야 한다.

https://tuffstuff9.hashnode.dev/zustand-create-vs-createstore