Zustand
Zustand는 독일어로 '상태(state)'를 의미한다.
React에서 사용할 수 있는 가볍고 간단한 상태 관리 라이브러리다.
Zustand는 React의 렌더 사이클과 훅 기반으로 작동한다.
observable 객체를 사용하는 다른 상태관리 도구(MobX 등)보다 observing이 약하지만 단순하고 React와 잘 통합된다는 장점이 있다.
핵심 개념
구분 | 설명 |
---|---|
Store | 앱 전체에서 공유할 상태와 로직을 정의한 공간 |
create | store를 생성하는 함수 |
useStore() | store에서 상태를 읽고 쓰기 위한 hook |
set() | store 상태 변경 |
subscribe() | 상태 변경을 구독 (옵셔널) |
사용법
스토어 생성
import { create } from 'zustand'
// 상태 생성
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
상태 사용 (컴포넌트에서)
function Counter() {
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>+1</button>
</div>
)
}
고급 패턴
선택적 구독 (성능 최적화)
필요한 속성만 구독 가능 → 불필요한 리렌더 방지.
const count = useStore((state) => state.count)
상태 외부에서 직접 접근
useStore.getState().increment()
상태 외부에서 구독
const unsub = useStore.subscribe(
(state) => state.count,
(count) => console.log('count 변경:', count)
)
특징 정리
특징 | 설명 |
---|---|
Hook 기반 | React의 useState 느낌 그대로 사용 |
복잡한 설정 필요 없음 | Redux 같은 boilerplate 없음 |
컴포넌트 외부에서도 접근 가능 | store.getState(), store.setState() 사용 가능 |
불변성 관리 자동화 | immer 사용 없이도 안전하게 상태 업데이트 가능 |
선택적 구독 가능 | selector 사용으로 리렌더 최적화 가능 |
미들웨어
Zustand는 Redux처럼 미들웨어를 플러그인처럼 사용할 수 있음.
예:
zustand/middleware
→devtools
,persist
,immer
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const useStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'my-store' }
)
)
)
요약
create(set => { ... })
로 store 생성useStore((state) => state.xxx)
로 상태 사용set()
으로 상태 변경- React 외부에서도
useStore.getState()
가능 - 선택적 구독, 구독자 직접 관리 가능
- 미들웨어로 persist, devtools, immer 확장 가능
Zustand Context Provider와 SSR
초기 상태와 함께 Zustand Context Provider 초기화(공식문서)
Next.js로 설정(공식문서)
Zustand는 기본적으로 전역 변수를 사용하나, Next.js의 hydration 에러를 해결하기 위해 Context
를 사용하여 SSR 친화적인 환경을 만들 수 있다.
먼저 아래와 같이 Store 객체를 만든다. (use-auth-store.tsx)
import { createStore } from "zustand";
import { Session, User } from "@supabase/supabase-js";
import { createClient } from "@/utils/supabase/client";
const supabase = createClient();
export interface AuthState {
user: User | null;
loading: boolean;
setUser: (user: User | null, session: Session | null) => void;
}
export const createAuthStore = (initialState?: Partial<AuthState>) =>
createStore<AuthState>((set) => ({
user: null,
loading: true,
setUser: (user, session) =>
set({
user,
loading: false,
}),
...initialState, // 초기값 덮어쓰기
}));
먼저 아래와 같이 Store 객체를 만든다. (use-auth-store.tsx)
import { createStore } from "zustand";
import { Session, User } from "@supabase/supabase-js";
import { createClient } from "@/utils/supabase/client";
const supabase = createClient();
export interface AuthState {
user: User | null;
loading: boolean;
setUser: (user: User | null, session: Session | null) => void;
}
export const createAuthStore = (initialState?: Partial<AuthState>) =>
createStore<AuthState>((set) => ({
user: null,
loading: true,
setUser: (user, session) =>
set({
user,
loading: false,
}),
...initialState, // 초기값 덮어쓰기
}));
아래와 같이 StoreProvider, useStore를 선언한다.
"use client";
import { createContext, useRef, useContext, ReactNode } from "react";
import { useStore } from "zustand";
import { createAuthStore, AuthState } from "@/hooks/zustand/use-auth-store";
export type AuthStoreApi = ReturnType<typeof createAuthStore>;
const AuthStoreContext = createContext<AuthStoreApi | undefined>(undefined);
interface AuthStoreProviderProps {
children: ReactNode;
initialState?: Partial<AuthState>;
}
export const AuthStoreProvider = ({
children,
initialState,
}: AuthStoreProviderProps) => {
const storeRef = useRef<AuthStoreApi>(null);
if (!storeRef.current) {
storeRef.current = createAuthStore(initialState);
}
return (
<AuthStoreContext.Provider value={storeRef.current}>
{children}
</AuthStoreContext.Provider>
);
};
export const useAuthStore = <T,>(selector: (store: AuthState) => T): T => {
const authStoreContext = useContext(AuthStoreContext);
if (!authStoreContext) {
throw new Error("useAuthStore must be used within AuthStoreProvider");
}
return useStore(authStoreContext, selector);
};
해당 useStore를 사용하려는 범위에서 storeProvider로 감싼다.
<html lang="ko-KR">
<body
className={`antialiased h-screen flex flex-col bg-background text-color-base font-sans scrollbar-hidden`}
>
<AuthStoreProvider>
{children}
</AuthStoreProvider>
</body>
</html>