Zustand란?
- React 전역 상태 관리 라이브러리
- Context API보다 간단하고 직관적
- Provider 없이 상태 관리 가능
- Redux, Recoil처럼 컴포넌트 간 상태 공유
- 브라우저 메모리에서만 관리되므로 새로고침 시 초기화됨
- 서버와 통신하지 않음 -> 서버 데이터를 가져와 캐싱하는 용도로 자주 활용
- 예) getDashboardList()로 서버에서 받아온 데이터를 Zustand에 저장 후 여러 컴포넌트에서 사용
설치
npm install zustand
기본 구조
create : Zustand 스토어 생성 함수 / state와 action 정의
state(상태) : 전역으로 관리할 데이터 / UI에서 표시할 데이터
action(액션) : 상태를 업데이트하는 함수 (내부에서 set 호출)
set : 상태를 업데이트 (React의 setState 느낌)
get : 현재 상태 가져오기
import { create } from "zustand";
interface StoreState {
count: number;
increase: () => void;
}
export const useCounterStore = create<StoreState>((set, get) => ({
count: 0, // 상태(state)
increase: () => set((state) => ({ count: state.count + 1 })), // 액션(action)
}));
set
- Zustand 스토어 생성 시 콜백에서 제공되는 함수
- 상태 업데이트에 사용 (useState와 유사하지만 더 간단)
- 객체 불변성 처리 자동 지원
- useState에서는 객체 상태를 바꿀 때 불변성 유지가 필수
- zustand는 새로운 객체를 반환하면 자동으로 상태를 갱신
- 불변성 = 원본을 직접 수정하지 않고, 새로운 값으로 교체하는 습관
// useState
setState((prev) => ({ ...prev, count: prev.count + 1 }));
// zustand
set({ count: state.count + 1 })
get
- 현재 상태를 가져올 때 사용
- 컴포넌트 외부 로직, 이벤트 핸들러 등에서도 상태 읽기 가능
- 전역 상태이므로 어디서든 접근 가능
getToken: () => get().accessToken,
컴포넌트에서 스토어 사용하기
getState() 사용
- 컴포넌트 외부 / 이벤트 핸들러 등 UI와 직접 연결되지 않은 로직에서 활용
- 구독하지 않음 → 상태 변경 시 리렌더링 발생하지 않음
const setAuth = useAuthStore.getState().setAuth;
setAuth(token, userId);
훅처럼 구독하기
- React 컴포넌트 내에서 상태/액션을 구독
- 상태 변경 시 리렌더링 발생
const setAuth = useAuthStore((state) => state.setAuth);
const accessToken = useAuthStore((state) => state.accessToken);
구조분해 할당
useAuthStore() 전체 구독 → store의 어떤 값이 바뀌든 리렌더링 발생
const { accessToken, setAuth } = useAuthStore(); // 전체 구독
구독이란?
- 컴포넌트가 특정 상태 변화를 감시하는 것
- Zustand에서 useAuthStore((state) => state.value)처럼 사용하면 구독 발생
→ 상태가 바뀌면 해당 컴포넌트가 리렌더링됨
로그인 예제
- 로그인 성공 시 accessToken과 userId를 전역 상태로 관리하기
- create를 사용해 zustand 전역 상태 스토어를 만들면, React 컴포넌트에서 사용할 수 있는 스토어 훅이 반환됨
import { create } from "zustand";
interface AuthState {
accessToken: string | null;
userId: number | null;
setAuth: (token: string, userId: number) => void;
clearAuth: () => void;
getToken?: () => string | null;
}
export const useAuthStore = create<AuthState>((set, get) => ({
accessToken: null,
userId: null,
// 로그인
setAuth: (token, userId) =>
set(() => ({
accessToken: token,
userId: userId,
})),
// 로그아웃
clearAuth: () =>
set(() => ({
accessToken: null,
userId: null,
})),
// 현재 토큰 조회
getToken: () => get().accessToken,
}));
컴포넌트 사용 예시
function Header() {
const accessToken = useAuthStore((state) => state.accessToken);
return <div>{accessToken ? "로그인됨" : "로그인 필요"}</div>;
}
function LoginButton() {
const setAuth = useAuthStore.getState().setAuth; // 구독X
const handleLogin = async () => {
const res = await postLogin();
setAuth(res.data.token, res.data.user.id);
};
return <button onClick={handleLogin}>로그인</button>;
}