Zustand를 사용하다 보면 middleware
라는 개념을 마주치게 됩니다. 처음에는 생소하지만, 조금만 이해하면 강력한 기능을 간단하게 붙일 수 있어요. 이 글에서는 Zustand 미들웨어가 뭔지, 왜 필요한지, 그리고 실전에서 어떻게 사용하는지를 예시와 함께 정리해볼게요.
Zustand의 미들웨어는 상태를 읽거나 수정하는 과정에서 중간에 끼어들어 어떤 작업을 추가할 수 있는 기능입니다.
const store = create(middleware((set, get) => ({
state: 'value',
update: () => set({ state: 'new value' }),
})));
상태가 set
으로 변경되기 전/후로 어떤 작업을 하고 싶을 때, 바로 이 미들웨어를 사용합니다.
사용 목적 | 설명 |
---|---|
🔍 디버깅/로깅 | 상태가 어떻게 바뀌었는지 콘솔에 찍어보기 |
💾 상태 저장 | localStorage나 sessionStorage에 저장해서 새로고침해도 복구 가능 |
🧪 개발자 도구 연동 | Chrome 확장에서 상태를 추적 (Redux Devtools와 비슷) |
⏱ 비동기 래핑 | 비동기 작업을 깔끔하게 다룰 수 있도록 확장 가능 |
🧼 공통 처리 | set 전에 유효성 검사, 에러 핸들링 등 로직을 한 곳에서 관리 |
devtools
– 개발자 도구 연동ts
복사편집
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create(devtools((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
})));
Chrome에서 Zustand Devtools 확장으로 상태 확인 가능!
persist
– 상태를 브라우저 저장소에 저장
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useUserStore = create(persist(
(set) => ({
user: null,
setUser: (user) => set({ user }),
}),
{
name: 'user-storage', // localStorage 키 이름
}
));
유저 정보, 다크모드 설정 등 새로고침해도 유지하고 싶은 상태에 활용하면 좋습니다.
const logger = (config) => (set, get, api) =>
config((args) => {
console.log('🧪 상태 변경 전:', get());
set(args);
console.log('✅ 상태 변경 후:', get());
}, get, api);
const useStore = create(logger((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
})));
직접 미들웨어를 만들어서 내가 원하는 기능을 상태 변경 흐름에 자연스럽게 붙일 수 있어요.
Zustand의 미들웨어는 체이닝이 가능합니다. 예를 들어 persist
와 devtools
를 동시에 쓰고 싶다면:
const useStore = create(
devtools(
persist((set) => ({
darkMode: false,
toggle: () => set((state) => ({ darkMode: !state.darkMode }))
}), {
name: 'settings-storage'
})
)
);
Zustand는 미들웨어 덕분에 단순한 상태 관리 그 이상을 할 수 있습니다. 특히 persist와 devtools는 실제 프로젝트에서 자주 쓰이는 필수 기능이고, 커스텀 미들웨어를 작성하면 내 프로젝트에 딱 맞는 상태 흐름을 만들 수 있어요.
복잡한 Redux 설정 없이도 유연하고 직관적인 상태 관리를 원한다면, Zustand + Middleware 조합은 정말 강추입니다.
이미 로컬스토리지 직접
쓰고 있는데, 굳이 Zustand의 persist
미들웨어를 왜 써야 할까?
결론 먼저 말하면:
✅ 로컬스토리지를 더 안정적이고, 간편하게, zustand랑 잘 연동되게 쓰고 싶다면 → persist 미들웨어 쓰는 게 훨씬 낫다.
persist
미들웨어 차이항목 | 직접 localStorage 사용 | zustand persist 미들웨어 사용 |
---|---|---|
데이터 저장/불러오기 | 직접 setItem , getItem 해야 함 | 자동으로 저장 & 불러오기 |
데이터 구조 관리 | JSON parse/stringify 필요 | 내부적으로 알아서 처리 |
상태와 분리 | 상태와 별도 관리, 복잡해짐 | 상태와 자연스럽게 통합됨 |
초기화 타이밍 | useEffect 로 따로 처리해야 함 | zustand 생성 시점에 알아서 초기화함 |
유지보수 및 테스트 | 따로 분리된 로직 관리 필요 | 미들웨어 내부에 다 캡슐화됨 |
devtools 연동 | 불가 | 가능 (persist + devtools 같이 사용 가능) |
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme') || 'light';
});
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
→ 이거 계속 반복되면 귀찮고 버그 생기기 쉬움.
→ 특히 다수 상태 동기화할 때 불일치
문제 생김.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useThemeStore = create(
persist(
(set) => ({
theme: 'light',
toggleTheme: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' }))
}),
{
name: 'theme-storage' // localStorage key
}
)
);
→ 이러면 한 줄도 없이 알아서 localStorage 연동돼.
→ 상태 관리 + 저장 + 초기 로드 + 일관성 유지까지 자동으로.
너가 지금 상태를 zustand로 잘 쓰고 있고, 상태를 저장할 필요가 있다면
굳이 수동으로 localStorage 관리하지 말고,
persist
미들웨어 써라.