Skip to content

Zustand 세팅

Zustand는 가볍고 직관적인 전역 상태 관리 라이브러리이다.
주로 클라이언트(UI) 상태를 중앙에서 관리하기 위해 사용한다.

왜 Zustand를 쓸까?

클라이언트 상태의 어려움

React에서 전역 상태를 단순히 Props + Context로만 관리하면 다음 문제가 자주 발생한다.

  • 여러 컴포넌트 간 상태 공유가 복잡해짐 (Props Drilling)
  • Context 남용 시 불필요한 리렌더링 증가
  • 상태 로직이 UI 컴포넌트에 흩어짐
  • 상태 구조가 커질수록 관리가 어려워짐
  • 디버깅이 어려움 (어디서 무엇이 바뀌었는지 추적이 힘듦)

이런 문제를 간결하고 실용적인 방식으로 해결해 주는 것이 Zustand다.


Zustand를 안 쓰면 무엇을 직접 해야 할까?

아래를 직접 구현해야 한다:

  • 전역 상태 저장소 설계
  • 상태 변경 로직 중앙화
  • 불필요한 리렌더링 방지
  • 상태 변경 추적(디버깅)
  • 상태 지속(persist) 로직
  • 상태 구조 확장 시 리팩토링

가능하지만, 앱이 커질수록 상태 구조가 복잡해지고 유지보수가 어려워진다.


언제 Zustand를 도입하면 좋을까?

다음과 같은 경우 도입을 권장한다:

  • 여러 화면에서 공통으로 사용하는 UI 상태가 있는 경우
    (로그인 정보, 테마, 사이드바 열림 여부, 모달 상태 등)
  • Redux가 과하게 무겁다고 느껴질 때
  • Context API의 성능 이슈가 걱정될 때
  • 상태 구조는 단순하지만 전역 공유가 필요한 경우
  • 보일러플레이트 없이 빠르게 상태를 관리하고 싶을 때

설치

bash
npm install zustand

기본 설정

실제 프로젝트에서는 src/store/ 폴더에 상태를 모아 관리하는 것이 좋다.

1. 기본 Store 예시

typescript
// src/store/ui.ts
import { create } from 'zustand';

interface UIState {
  isSidebarOpen: boolean;
  toggleSidebar: () => void;
  setSidebar: (open: boolean) => void;
}

export const useUIStore = create<UIState>((set) => ({
  isSidebarOpen: false,

  toggleSidebar: () =>
    set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),

  setSidebar: (open) => set({ isSidebarOpen: open }),
}));

2. 컴포넌트에서 사용

tsx
import { useUIStore } from '@/store/ui';

function SidebarToggle() {
  const { isSidebarOpen, toggleSidebar } = useUIStore();

  return (
    <button onClick={toggleSidebar}>{isSidebarOpen ? '닫기' : '열기'}</button>
  );
}

3. 필요한 상태만 구독하기 (성능 최적화)

전체 상태를 가져오면 불필요한 리렌더링이 발생할 수 있다.

tsx
// 나쁜 예: 전체 상태 구독 (모든 변경에 리렌더링)
const { isSidebarOpen, toggleSidebar } = useUIStore();

// 좋은 예: 필요한 값만 선택적 구독
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);

권장 방식: selector로 필요한 값만 선택해서 구독한다.


4. Persist(로컬 저장) 설정 (선택)

새로고침 후에도 상태를 유지하고 싶다면:

ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  token: string | null;
  setToken: (token: string) => void;
  clearToken: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      setToken: (token) => set({ token }),
      clearToken: () => set({ token: null }),
    }),
    {
      name: 'auth-storage',
    }
  )
);
  • 보통 인증 정보, 테마, 사용자 설정 등은 persist를 사용하고, 서버 데이터는 TanStack Query에 맡기는 것이 권장된다.

Zustand의 장단점

장점

  • 매우 가볍고 빠름
  • 보일러플레이트가 거의 없음
  • React와 자연스럽게 결합
  • Context보다 성능적으로 유리
  • Redux보다 훨씬 단순

단점

  • 대규모 상태 구조에서는 설계가 중요
  • 상태가 많아지면 폴더 구조 정리가 필요
  • 미리 규칙을 정하지 않으면 혼잡해질 수 있음

언제 Zustand를 안 써도 될까?

  • 컴포넌트 내부 상태만 필요한 경우 (useState로 충분)
  • 상태 공유가 거의 없는 작은 앱
  • 서버 상태가 대부분인 프로젝트(TanStack Query로 충분한 경우)

Zustand vs TanStack Query

ZustandTanStack Query
용도클라이언트 상태서버 상태
예시UI 상태, 사용자 설정API 응답 데이터
캐싱수동 관리자동 캐싱
리페칭없음자동 리페칭

TanStack Query는 서버 상태, Zustand는 UI 상태를 담당한다.
두 도구를 역할에 맞게 분리하면 코드가 훨씬 단순해진다.
즉, 서버 데이터는 Query, 화면 상태는 Store로 명확히 구분한다.