본문 바로가기
4주차

Zustand 전역 상태 관리: 개념부터 실무까지 한 번에 정리

by karnelll 2025. 5. 13.
Zustand 한 번에 정리: 기본 개념 → 실무 패턴

Zustand 한 번에 정리: 기본 개념 → 실무 패턴

안녕하세요, 솝트 웹 파트 36기 최서희입니다. 👋
4주차에는 Zustand에 대해 정리했습니다. React에서 전역 상태 관리는 더 이상 선택이 아닌 필수!
props drilling이 심해질수록 더 나은 전략이 필요하고, 그 대안 중 하나가 바로 Zustand입니다.

React에서 전역 상태를 간단·빠르게 공유할 수 있도록 도와주는 가벼운 상태 관리 라이브러리입니다. 이 글에서는 개념 요약 + 실전 코드 + 적용 팁을 한 번에 정리합니다. 스크랩해두고 꼭 복습하세요! 🔥

1️⃣ Zustand란?

“Zustand”는 독일어로 ‘상태(state)’. 훅 하나로 전역 상태를 다루는 것이 특징입니다.

✅ 핵심 특징

특징설명
보일러플레이트 없음Redux처럼 복잡한 설정 없이 간단하게 구성
빠름얕은 구독(선택적 구독) 기반으로 불필요 렌더 최소화
직관적단일 파일 store 정의 → 훅으로 바로 사용

2️⃣ 기본 사용 구조

create()로 저장소를 만들고, 컴포넌트에서는 일반 훅처럼 구독해 사용합니다.

예시: 카운터 상태 관리

// src/store/useCounterStore.ts
import { create } from "zustand";

interface CounterState {
  count: number;
  increase: () => void;
  decrease: () => void;
  reset: () => void;
}

export const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));
// 컴포넌트에서 사용
import { useCounterStore } from "@/store/useCounterStore";

export default function Counter() {
  const { count, increase, decrease, reset } = useCounterStore();
  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={increase}>+1</button>
      <button onClick={decrease}>-1</button>
      <button onClick={reset}>리셋</button>
    </div>
  );
}

3️⃣ 실무에서의 패턴

1) 비동기 API 처리

Zustand 내부에서 async/await를 사용해 간단히 API를 호출하고 상태를 갱신할 수 있습니다.

// src/store/useUserStore.ts
import { create } from "zustand";
import axios from "axios";

interface UserState {
  user: string | null;
  isLoading: boolean;
  error: string | null;
  fetchUser: () => Promise<void>;
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  isLoading: false,
  error: null,
  fetchUser: async () => {
    set({ isLoading: true });
    try {
      const res = await axios.get("/api/user");
      set({ user: res.data.name, isLoading: false });
    } catch (e) {
      set({ error: "유저 정보를 가져오지 못했습니다.", isLoading: false });
    }
  },
}));

2) shallow 비교로 최적화

여러 상태를 동시에 구독할 때 shallow를 쓰면 불필요 렌더를 줄일 수 있습니다.

import { useUserStore } from "@/store/useUserStore";
import { shallow } from "zustand/shallow";

const [user, isLoading] = useUserStore(
  (state) => [state.user, state.isLoading],
  shallow
);

3) Middleware 활용

  • persist: localStorage 등 영속 저장
  • devtools: Redux DevTools 연동
  • subscribeWithSelector: 특정 상태 변화 감지
// src/store/useThemeStore.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";

interface ThemeState {
  isDark: boolean;
  toggleTheme: () => void;
}

export const useThemeStore = create(
  persist<ThemeState>(
    (set) => ({
      isDark: false,
      toggleTheme: () => set((state) => ({ isDark: !state.isDark })),
    }),
    { name: "theme-storage" } // localStorage key
  )
);

4) React Query와 병행 사용

서버 상태데이터 fetch, 캐시, 동기화 → React Query
클라이언트 상태UI·폼·탭·모달 등 → Zustand
// 서버 상태: React Query
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

const fetchPosts = () => axios.get("/api/posts").then((res) => res.data);
export const usePosts = () => useQuery({ queryKey: ["posts"], queryFn: fetchPosts });

// 클라이언트 상태: Zustand
import { create } from "zustand";

interface TabState {
  selectedTab: string;
  setTab: (tab: string) => void;
}

export const useTabStore = create<TabState>((set) => ({
  selectedTab: "all",
  setTab: (tab) => set({ selectedTab: tab }),
}));
참고
shallowimport { shallow } from "zustand/shallow" (버전에 따라 default export가 아닐 수 있습니다).
• 위 useTabStore 예제는 set 파라미터를 명시해 사용하도록 보정했습니다.

4️⃣ 사용 시 주의할 점

항목설명
store 분리하나의 store에 모든 상태 몰아넣지 말고 기능별로 분리
필요한 값만 구독selector로 필요한 조각만 구독, shallow로 렌더 최적화
select + shallow리렌더 핫스팟에 필수 전략
사이드 이펙트 분리API 호출 등은 service 모듈로 분리해 테스트/재사용성↑

5️⃣ Zustand가 실무에 적합한 이유

  • Context API보다 구조적이고 예측 가능
  • Redux 대비 훨씬 가볍고 보일러플레이트가 적음
  • 소규모 → 대규모 앱까지 유연하게 확장
  • 모달, 필터, 탭, 테마, 인증 등 UI 상태 관리에 최적

'4주차' 카테고리의 다른 글

네이밍 규칙  (1) 2025.05.13
CommonJS와 ES Modules  (0) 2025.05.13
Tailwind CSS 버전 정리  (0) 2025.05.13
Tanstack Query  (0) 2025.05.13
Lazy loading과 Suspense  (0) 2025.05.13