본문 바로가기

TIL

TIL - 전역상태관리 (Zustand)

01. 설치하기

npm install zustand

 

02. 사용법

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

function Counter() {
  const { count, inc } = useStore()
  return (
    <div>
      <span>{count}</span>
      <button onClick={inc}>one up</button>
    </div>
  )
}

스토어는 훅입니다. 여기에는 객체, 함수 등 무엇이든 넣을 수 있습니다. 상태는 불변적으로 업데이트되어야 하며, 설정 함수는 이를 돕기 위해 상태를 병합합니다.

 

03. 유의할 점

Fetching everything vs. Selecting multiple state slices

Fetching everything

const state = useBearStore()

상태가 변경될 때마다 컴포넌트가 업데이트됩니다.

Selecting multiple state slices

const nuts = useBearStore((state) => state.nuts)
const honey = useBearStore((state) => state.honey)

기본적으로 엄격한 동일성(이전상태 === 새로운 상태)으로 변경 사항을 감지해서 state.nuts의 실행 결과를 기억하고 이전 결과가 다르지 않다면 리렌더링이 되지 않아서 불필요한 렌더링을 방지할 수 있습니다.

 

const { nuts, honey } = useBearStore(
  useShallow((state) => {{
    nuts: state.nuts,
    honey: state.honey,
  }))
);

zustand에서 useStore를 사용해서 여러 개의 값을 꺼낼 때에는 한번에 각각 사용해서 따로 꺼내는 방법이 있고 한 번에 객체 또는 배열 형태로 꺼내려면 useShallow를 사용해야 합니다.

 

Async action을 기본으로 지원

준비가 되면 set값을 호출하기만 하면 zustand는 작업의 비동기화 여부에 상관하지 않습니다.

const useFishStore = create((set) => ({
  fishies: {},
  fetch: async (pond) => {
    const response = await fetch(pond)
    set({ fishies: await response.json() })
  },
}))

 

 

Immer.js의 필요성


set 함수는 상태를 한 개의 깊이만 병합합니다. 중첩된 개체가 있는 경우 명시적으로 병합해야 합니다. 다음과 같이 스프레드 연산자 패턴을 사용합니다.

import { create } from 'zustand'

const useCountStore = create((set) => ({
  nested: { count: 0 },
  inc: () =>
    set((state) => ({
      nested: { ...state.nested, count: state.nested.count + 1 },
    })),
}))

 

중첩된 구조를 줄이는 것은 번거로운 작업으로 immer를 사용하면 이 문제를 해결할 수 있습니다.

 

// immer의 기본 사용법
import { produce } from 'immer'

const useLushStore = create((set) => ({
  lush: { forest: { contains: { a: 'bear' } } },
  clearForest: () =>
    set(
      produce((state) => {
        state.lush.forest.contains = null
      }),
    ),
}))

const clearForest = useLushStore((state) => state.clearForest)
clearForest()

 

produce 함수의 첫 번째 매개변수로 전달된 함수는 수정할 로직을 포함하는 함수이며, 이 함수의 인자 state는 원본 상태입니다. 해당 함수 내에서 state를 변경하는 것은 수정할 로직을 작성하는 부분을 레시피라고 부릅니다.

 

Immer middleware

Immer 미들웨어를 사용하면 보다 편리한 방식으로 불변 상태를 사용할 수 있습니다. 또한 Immer를 사용하면 Zustand에서 변경 불가능한 데이터 구조를 간편하게 처리할 수 있습니다.

 

사용 예시

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

type State = {
  count: number
}

type Actions = {
  increment: (qty: number) => void
  decrement: (qty: number) => void
}

export const useCountStore = create<State & Actions>()(
  immer((set) => ({
    count: 0,
    increment: (qty: number) =>
      set((state) => {
        state.count += qty
      }),
    decrement: (qty: number) =>
      set((state) => {
        state.count -= qty
      }),
  })),
)

 

Persist middleware

browser storage에 데이터 보관하기

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    },
  ),
)

 

모든 종류의 스토리지를 사용하여 스토어의 데이터를 유지할 수 있습니다.