React 생태계에서 가장 핫한 상태 관리 라이브러리, Zustand.
Redux의 복잡함에 지치고, Recoil의 업데이트 중단 소식에 불안해하는 개발자들에게 Zustand는 가장 매력적인 대안이 되었습니다.
오늘은 Zustand를 실무에서 제대로 쓰기 위해 꼭 알아야 할 핵심 Middleware 3대장과 비동기 처리(Async), 그리고 Recoil과의 근본적인 차이점까지 한 번에 정리해 봅니다.
1. Zustand가 강력해지는 순간: Middleware 3대장
Zustand는 기본적으로 매우 가볍지만, 미들웨어를 장착하면 강력한 기능을 손쉽게 확장할 수 있습니다. 가장 자주 쓰이는 3가지(persist, devtools, combine)를 소개합니다.
① persist (새로고침 해도 데이터 유지)
브라우저의 localStorage나 sessionStorage에 상태를 자동으로 저장하고 복구해줍니다. 로그인 토큰이나 다크모드 설정, 장바구니 데이터 유지에 필수적입니다.
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useStore = create(
persist(
(set) => ({
theme: 'light',
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
}),
{
name: 'app-settings', // 로컬 스토리지에 저장될 Key 이름
// partialize: (state) => ({ theme: state.theme }), // 특정 필드만 저장하고 싶을 때
}
)
)
② devtools (Redux DevTools 연결)
상태가 언제, 왜 변했는지 추적하기 위해 Chrome의 Redux DevTools 확장 프로그램과 연결해줍니다. 타임 트래블(과거 상태로 되돌리기) 디버깅도 가능합니다.
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const useStore = create(
devtools(
(set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'MyStore' } // DevTools 창에 표시될 이름
)
)
③ combine (TypeScript 타입 자동 추론)
TypeScript 사용 시, 상태(State)와 액션(Actions)의 타입을 일일이 정의하기 귀찮을 때 유용합니다. 초기값과 액션을 분리하면 타입을 자동으로 추론해줍니다.
import { create } from 'zustand'
import { combine } from 'zustand/middleware'
const useStore = create(
combine(
{ count: 0, name: 'Guest' }, // 초기 상태값 (타입 자동 추론)
(set) => ({
inc: () => set((state) => ({ count: state.count + 1 })),
setName: (name: string) => set({ name })
})
)
)
꿀팁: 실무에서는 보통 이들을 조합(Chaining)해서 사용합니다. 순서는 devtools(persist(...)) 형태를 권장합니다.
2. Zustand의 비동기 처리 (Async)
"Zustand에는 Thunk나 Saga 같은 게 없나요?"
네, 필요 없습니다. Zustand는 자바스크립트의 async/await 문법을 그대로 지원하기 때문입니다.
얼마나 간단한가요?
그냥 함수 앞에 async를 붙이고 내부에서 await 후 set을 호출하면 끝입니다.
const useAuthStore = create((set) => ({
user: null,
isLoading: false,
login: async (email, password) => {
set({ isLoading: true }); // 로딩 시작
try {
const res = await api.login(email, password);
set({ user: res.data, isLoading: false }); // 성공 시 상태 업데이트
} catch (error) {
set({ error: error.message, isLoading: false }); // 에러 처리
}
}
}));
💡 실무 Tip: React Query와의 공존
Zustand로 모든 비동기 상태를 관리할 수도 있지만, 최근 트렌드는 역할 분담이 확실합니다.
- Zustand: 클라이언트 상태 (로그인 여부, 모달 Open, UI 테마 등)
- React Query: 서버 상태 (게시판 목록, 상품 리스트 등 캐싱이 필요한 데이터)
3. 번외: Recoil에는 왜 미들웨어가 없을까?
Recoil을 쓰다가 넘어온 분들이 가장 많이 묻는 질문입니다.
"Recoil은 persist 같은 미들웨어가 왜 기본으로 없나요?"
이것은 라이브러리의 철학(Philosophy) 차이 때문입니다.
| 구분 | Zustand | Recoil |
| 구조 | 거대한 하나의 Store (중앙집중형) | 잘게 쪼개진 Atom들의 그래프 (분산형) |
| 확장 방식 | Store 전체를 감싸는 Middleware | 개별 Atom에 주입하는 Atom Effects |
| 비동기 | JS 로직 (async/await) | React 기능 (Suspense + Selector) |
| 접근성 | React 몰라도 JS만 알면 이해 가능 | React의 심화 개념(Suspense 등) 이해 필요 |
Recoil은 "React스러운(Reactish)" 상태 관리를 지향하기 때문에, 중앙에서 통제하는 미들웨어보다는 개별 Atom의 생명주기를 관리하는 Effects와 React의 Suspense 기능을 활용하도록 설계되었습니다. 반면 Zustand는 "가장 단순하고 직관적인" 해결책을 제시합니다.
마무리
Zustand는 "작고(Small), 빠르고(Fast), 확장 가능한(Scalable)" 곰(Bear)입니다.
복잡한 설정 없이 바로 시작할 수 있지만, Middleware를 활용하면 대규모 애플리케이션도 충분히 감당할 수 있습니다.
지금 새로운 프로젝트를 시작한다면, Zustand가 가장 합리적인 선택이 될 것입니다.
'ReactJs > Library' 카테고리의 다른 글
| ReactJs [유용한 라이브러리 모음 및 설명] (0) | 2020.06.21 |
|---|