Skip to content

Zustand Store

💡 Point

  • Zustand는 상태(state)와 로직(action)을 한 곳에 모아 관리할 수 있는 가벼운 상태 관리 라이브러리다.
  • useAppStore라는 훅을 만들어두면, 컴포넌트 안에서 바로 불러와서 값이나 함수를 쓸 수 있다.
    • useAppStore()는 훅이므로 무조건 함수처럼 호출해야 한다.
    • useAppStore((state) => state.count) 같이 선택자를 쓰면 필요한 값만 구독해서 불필요한 리렌더링을 줄일 수 있다.
    • 한 번 만든 스토어는 앱 전체에서 단일 인스턴스(싱글톤)로 동작해, 여러 컴포넌트가 같은 상태를 공유할 수 있다.

1. Store 생성하기

useAppStore는 단순한 변수처럼 보이지만, 실제로는 React에서 실행 가능한 커스텀 훅이다.
컴포넌트 안에서 호출하면 Zustand가 자동으로 구독을 걸고, 상태가 바뀌면 컴포넌트를 다시 렌더링한다.

tsx
import { create } from "zustand";

interface StoreState {
  count: number; // 상태 (state)
  increment: () => void; // 상태를 바꾸는 함수 (action)
}

const useAppStore = create<StoreState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

export default useAppStore;
  • create: Zustand의 핵심 함수, store를 생성한다.
  • set: Zustand가 제공하는 상태 업데이트 함수.
  • count : 상태(state) → 현재 값 저장소 (여기선 기본값 0)
  • increment: action → set을 이용해 count를 1 증가시키는 함수
🧩 타입 설계 TypeScript

타입스크립트 문법

ts
interface StoreState {
  count: number; // 숫자 타입
  increment: () => void; // 인자 없음, 반환값 없음
}

StoreState라는 이름의 타입 설계도 생성

  • count: 반드시 숫자여야 한다.
  • increment: 반드시 함수여야 하며, 매개변수와 반환값이 없다.

이렇게 타입을 정의하면, store.count = "abc" 같은 실수를 컴파일 단계에서 막을 수 있다.


자바스크립트라면?

js
const store = {
  count: 0,
  increment: function () {
    store.count += 1;
  },
};

JS에서는 구조만 맞으면 문제없이 동작한다. 하지만 TS는 타입 규칙을 엄격히 지켜야 하므로 안정성이 높아진다.


2. 컴포넌트에서 store 사용하기

tsx
import useAppStore from "./store/useAppStore";

function App() {
  const { count, increment } = useAppStore();

  return (
    <div>
      <h1>Hello Zustand</h1>
      <p>{count}</p>

      <button onClick={increment}>increment</button>
    </div>
  );
}

export default App;
tsx
import useAppStore from "./store/useAppStore";

function App() {
  // 1. 각각 꺼내 쓰는 방법 (선택자 사용)
  // const count = useAppStore((state) => state.count);
  // const increment = useAppStore((state) => state.increment);

  // 2. 구조분해 할당으로 한 번에 꺼내기 (스토어 전체에서 꺼냄)
  const { count, increment } = useAppStore();

  return (
    <div>
      <h1>Hello Zustand</h1>
      <p>{count}</p>

      {/* 화살표 함수로 감쌀 필요 없음 */}
      {/* <button onClick={() => increment()}>increment</button> */}

      {/* 함수 레퍼런스를 직접 전달 (increment가 함수이므로 그대로 전달 가능) */}
      <button onClick={increment}>increment</button>
    </div>
  );
}

export default App;
tsx
import { create } from "zustand";

interface StoreState {
  count: number;
  increment: () => void;
}

const useAppStore = create<StoreState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

export default useAppStore;
  • onClick={increment}: 버튼이 클릭되면 increment 실행
  • onClick={() => increment()}: 버튼 클릭 시 익명 함수 실행, 그 안에서 increment() 호출

현재 코드에선 두 방식이 동일하지만, 매개변수를 넘겨야 할 때는 화살표 함수가 필요하다.


상태 구독 방식

  • useAppStore((state) => state.count): count만 구독
  • useAppStore((state) => state.increment): increment만 구독
  • useAppStore(): 스토어 전체를 가져와 필요한 값 꺼냄

필요한 값만 선택적으로 구독하면 불필요한 리렌더링을 줄일 수 있다.


실행 흐름 요약

  1. 앱 시작 → useAppStore 생성 (count=0, increment 준비됨)
  2. App 컴포넌트가 count 구독
  3. 버튼 클릭 → increment 실행 → count + 1
  4. Zustand가 값 변경 감지 → 컴포넌트 리렌더링
  5. 새로운 count 값이 화면에 반영

숨겨진 유틸

Zustand store는 훅이면서 동시에 스토어 핸들을 가진다.

tsx
useAppStore.getState()    // 현재 상태 가져오기
useAppStore.setState(...) // 상태 직접 변경
useAppStore.subscribe(...)// 상태 변경 구독

즉, React 훅처럼 쓰면서도, 리액트 바깥에서도 상태를 제어할 수 있다.