미들웨어: devtools, persist, immer

Zustand는 미들웨어로 기능을 확장할 수 있습니다. 가장 많이 쓰이는 세 가지를 살펴봅니다.

devtools: Redux DevTools 연결

브라우저의 Redux DevTools 확장을 통해 상태 변화를 추적할 수 있습니다.

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
 
const useCountStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }), false, 'increment'),
      decrement: () => set((state) => ({ count: state.count - 1 }), false, 'decrement'),
    }),
    { name: 'CountStore' } // DevTools에서 표시될 스토어 이름
  )
);

set의 세 번째 인자로 액션 이름을 지정하면 DevTools 에서 어떤 액션이 상태를 바꿨는지 추적할 수 있습니다.

DevTools에서 보이는 것:
▶ increment   { count: 0 → 1 }
▶ increment   { count: 1 → 2 }
▶ decrement   { count: 2 → 1 }

persist: 상태 영속화

페이지를 새로고침해도 상태를 유지하고 싶을 때 사용합니다. 기본적으로 localStorage에 저장합니다.

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
 
const useSettingsStore = create(
  persist(
    (set) => ({
      theme: 'light',
      language: 'ko',
      fontSize: 14,
 
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
    }),
    {
      name: 'user-settings', // localStorage 키 이름
    }
  )
);

앱을 새로고침하면 localStorage에서 이전 상태를 복원합니다.

일부 상태만 저장하기

민감한 정보나 임시 상태는 저장에서 제외할 수 있습니다.

const useAuthStore = create(
  persist(
    (set) => ({
      user: null,
      token: null,
      isLoading: false, // 저장 안 함
      error: null,      // 저장 안 함
 
      setAuth: (user, token) => set({ user, token }),
      logout: () => set({ user: null, token: null }),
    }),
    {
      name: 'auth-store',
      // 저장할 상태만 지정
      partialize: (state) => ({
        user: state.user,
        token: state.token,
      }),
    }
  )
);

sessionStorage 사용

persist(
  (set) => ({ ... }),
  {
    name: 'session-store',
    storage: {
      getItem: (name) => sessionStorage.getItem(name),
      setItem: (name, value) => sessionStorage.setItem(name, value),
      removeItem: (name) => sessionStorage.removeItem(name),
    },
  }
)

immer: 불변성 없이 직접 수정

중첩된 상태를 업데이트할 때 spread 연산자를 여러 번 쓰는 것이 불편하다면, Immer 미들웨어를 사용합니다. 마치 직접 수정하는 것처럼 쓰면 Immer가 알아서 불변 업데이트를 처리합니다.

npm install immer
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
 
const useUserStore = create(
  immer((set) => ({
    user: {
      name: '홍길동',
      address: {
        city: '서울',
        district: '강남구',
      },
    },
    todos: [],
 
    // Immer 없이: spread 연산자 중첩
    // setCity: (city) => set((state) => ({
    //   user: { ...state.user, address: { ...state.user.address, city } }
    // })),
 
    // Immer 있음: 직접 수정처럼 작성
    setCity: (city) =>
      set((state) => {
        state.user.address.city = city; // 직접 수정!
      }),
 
    addTodo: (text) =>
      set((state) => {
        state.todos.push({ id: Date.now(), text, completed: false }); // push 사용!
      }),
 
    toggleTodo: (id) =>
      set((state) => {
        const todo = state.todos.find(t => t.id === id);
        if (todo) todo.completed = !todo.completed; // 직접 수정!
      }),
  }))
);

미들웨어 조합

여러 미들웨어를 함께 사용할 수 있습니다.

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
 
const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        items: [],
        addItem: (item) =>
          set((state) => {
            state.items.push(item);
          }),
      })),
      { name: 'my-store' }
    ),
    { name: 'MyStore' }
  )
);

순서 권장: devtools(persist(immer(...)))

TypeScript와 함께

interface BearState {
  count: number;
  increment: () => void;
}
 
const useBearStore = create<BearState>()(
  devtools(
    persist(
      (set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 })),
      }),
      { name: 'bear-store' }
    )
  )
);

TypeScript 를 쓸 때는 create<Type>()(...) 형태로 타입을 명시합니다. 미들웨어가 있으면 create<Type>() 뒤에 바로 미들웨어를 감쌉니다.

정리

미들웨어역할주요 옵션
devtoolsRedux DevTools 연결name: 스토어 이름
persistlocalStorage 영속화name: 키, partialize: 저장할 것
immer직접 수정 문법 허용없음

세 가지 모두 실무에서 자주 쓰입니다. devtools 는 개발 편의를 위해, persist 는 새로고침 후 상태 복원을, immer 는 중첩 상태 업데이트를 간결하게 만들어줍니다.