키모형 2026. 1. 18. 22:15
반응형

1. Zustand란?

Zustand는 React에서 전역 상태를 매우 간단하게 관리할 수 있는 라이브러리입니다.

특징

  • Redux처럼 복잡한 보일러플레이트가 거의 없습니다.
  • Context Provider를 필수로 만들지 않아도 됩니다(대부분의 경우).
  • Store가 작고 가벼우며, 필요한 컴포넌트만 선택적으로 구독할 수 있습니다.
  • 전역 상태를 “훅 형태”로 바로 불러다 쓰는 방식이 직관적입니다.

2. 설치 방법

아래 중 프로젝트에서 사용하는 패키지 매니저로 설치하시면 됩니다.

pnpm
pnpm add zustand

npm
npm i zustand

yarn
yarn add zustand

3. Zustand 기본 개념

Zustand는 크게 2가지만 이해하시면 됩니다.

  1. Store 생성: create()로 전역 상태와 액션(변경 함수)을 정의합니다.
  2. 구독(사용): 컴포넌트에서 useStore((state) => state.무엇) 형태로 필요한 값만 가져옵니다.

4. 예제: 사용자 정보(user)를 전역 상태로 관리하기

목표는 다음과 같습니다.

  • 전역 상태: user = { email, name }
  • 액션: setEmail, setName, setUser, reset

4-1) Store 파일 생성

예시 경로: /lib/stores/user.store.ts

"use client";

import { create } from "zustand";

export type User = {
  email: string;
  name: string;
};

type UserStore = {
  user: User;
  setEmail: (email: string) => void;
  setName: (name: string) => void;
  setUser: (user: User) => void;
  reset: () => void;
};

const initialUser: User = { email: "", name: "" };

export const useUserStore = create<UserStore>((set) => ({
  user: initialUser,

  // email만 변경
  setEmail: (email) =>
    set((state) => ({ user: { ...state.user, email } })),

  // name만 변경
  setName: (name) =>
    set((state) => ({ user: { ...state.user, name } })),

  // user 통째로 교체
  setUser: (user) => set({ user }),

  // 초기화
  reset: () => set({ user: initialUser }),
}));

 

5. Store 사용법(기본 패턴)

5-1) 값 조회(읽기)

 
const user = useUserStore((s) => s.user);

5-2) 필요한 값만 선택 조회(권장)

 
const email = useUserStore((s) => s.user.email);
const name = useUserStore((s) => s.user.name);

이 방식이 좋은 이유는, 컴포넌트가 “정말 필요한 값”이 바뀔 때만 리렌더되기 때문입니다.

5-3) 액션(변경 함수) 가져오기

 
const setEmail = useUserStore((s) => s.setEmail);
const reset = useUserStore((s) => s.reset);

 


6. 실제 페이지 예제: 입력 폼에서 전역 상태 업데이트

/app/users/update/page.tsx

"use client";

import { useRouter } from "next/navigation";
import { useUserStore } from "@/lib/stores/user.store";

export default function UsersUpdatePage() {
  const router = useRouter();

  const user = useUserStore((s) => s.user);
  const setEmail = useUserStore((s) => s.setEmail);
  const setName = useUserStore((s) => s.setName);

  return (
    <main className="mx-auto max-w-xl p-6">
      <div className="rounded-2xl border bg-white p-6 shadow-sm">
        <h1 className="text-xl font-semibold">Users Update Page</h1>

        <div className="mt-6 space-y-3">
          <input
            type="email"
            value={user.email}
            placeholder="Enter your Email"
            onChange={(e) => setEmail(e.target.value)}
            className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm
                       focus:border-gray-400 focus:ring-4 focus:ring-gray-100 outline-none"
          />

          <input
            type="text"
            value={user.name}
            placeholder="Enter your Name"
            onChange={(e) => setName(e.target.value)}
            className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm
                       focus:border-gray-400 focus:ring-4 focus:ring-gray-100 outline-none"
          />

          <div className="flex gap-2 pt-2">
            <button
              type="button"
              onClick={() => router.push("/users/updated-user")}
              className="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm
                         hover:bg-gray-50 active:bg-gray-100"
            >
              Update
            </button>

            <button
              type="button"
              onClick={() => useUserStore.getState().reset()}
              className="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm
                         hover:bg-gray-50 active:bg-gray-100"
            >
              Reset
            </button>
          </div>
        </div>
      </div>
    </main>
  );
}

 

포인트

  • Update 버튼에서 router.push("/users/updated-user")로 이동하면 같은 탭에서 클라이언트 네비게이션이 일어나므로 전역 상태가 유지됩니다.
  • Reset은 useUserStore.getState().reset()처럼 “이벤트 시점에 한 번 호출”하는 패턴도 가능합니다.

7. 전역 상태 확인 페이지 예제

/app/users/updated-user/page.tsx

"use client";

import { useUserStore } from "@/lib/stores/user.store";

export default function UpdatedUserPage() {
  const user = useUserStore((s) => s.user);

  return (
    <main className="mx-auto max-w-xl p-6">
      <div className="rounded-2xl border bg-white p-6 shadow-sm">
        <h1 className="text-xl font-semibold">Updated User Page</h1>

        <div className="mt-4 space-y-2 text-sm">
          <div>
            <span className="font-medium">User Name :</span> {user.name}
          </div>
          <div>
            <span className="font-medium">User Email :</span> {user.email}
          </div>
        </div>
      </div>
    </main>
  );
}

 

8. (선택) 새로고침해도 상태를 유지하고 싶을 때(persist)

Zustand 기본 store는 브라우저 메모리에만 존재합니다.
즉, 새로고침(Full Reload)을 하면 초기화됩니다.

만약 새로고침에도 유지하고 싶다면 persist 미들웨어를 사용합니다.

"use client";

import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";

export type User = { email: string; name: string };

type UserStore = {
  user: User;
  setEmail: (email: string) => void;
  setName: (name: string) => void;
  reset: () => void;
};

const initialUser: User = { email: "", name: "" };

export const useUserStore = create<UserStore>()(
  persist(
    (set) => ({
      user: initialUser,
      setEmail: (email) => set((s) => ({ user: { ...s.user, email } })),
      setName: (name) => set((s) => ({ user: { ...s.user, name } })),
      reset: () => set({ user: initialUser }),
    }),
    {
      name: "user-store",
      storage: createJSONStorage(() => localStorage),
    }
  )
);

 

9. 마무리 정리

  • Zustand는 설치와 사용이 간단하고, Next.js App Router에서도 부담 없이 적용할 수 있습니다.
  • 전역 상태는 create()로 만들고, 페이지/컴포넌트에서는 useUserStore(selector)로 필요한 값만 가져오면 됩니다.
  • 페이지 이동 시 상태가 유지되려면 Next.js의 클라이언트 네비게이션(Link/router.push) 흐름을 사용해야 하며,
  • 새로고침에도 유지하려면 persist를 적용하면 됩니다.
반응형