Skip to content
Store 详解

Store 详解

本章深入 Zustand Store 的创建方式、状态操作 API 和组织模式。


set 的完整用法

基础:传入对象(浅合并)

const useStore = create((set) => ({
  name: 'Alice',
  age: 30,
  setName: (name: string) => set({ name }),         // 只更新 name
  setAge: (age: number) => set({ age }),             // 只更新 age
}));

每次 set({ name }) 都是浅合并:只覆盖传入的字段,其他字段保持不变。

函数式更新(依赖前值)

const useCounter = create<{ count: number; inc: () => void }>((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),  // 基于当前值
  // 连续三次 inc() 会得到 3,而不是 1
}));

🚨 陷阱:永远不要在 set 外部读取 state 来构建新状态:

// ❌ 错误:闭包中的 state 可能是旧的
inc: () => set({ count: state.count + 1 })  // state 从哪来?

// ✅ 正确:使用 set 的回调形式
inc: () => set((state) => ({ count: state.count + 1 }))

replace:替换整个 state

// 第二个参数为 true → 完全替换,不合并
set({ count: 0, name: 'Bob' }, true);
// 整个 state 变成 { count: 0, name: 'Bob' },之前的所有字段都丢失

这个能力让 Zustand 可以轻松支持状态重置

interface AppState {
  user: User | null;
  theme: 'light' | 'dark';
  // ...
}

const initialState: AppState = { user: null, theme: 'light' };

const useAppStore = create<AppState & { reset: () => void }>((set) => ({
  ...initialState,
  setUser: (user) => set({ user }),
  setTheme: (theme) => set({ theme }),
  // 一键重置所有状态
  reset: () => set(initialState, true),   // replace = true
}));

Action name(DevTools 友好)

// 第三个参数在 DevTools 中作为 action name 显示
inc: () =>
  set(
    (state) => ({ count: state.count + 1 }),
    false,           // replace(默认 false)
    'increment'      // action name
  ),

get 的完整用法

get() 返回最新的 state 快照,是同步的:

const useStore = create<{
  count: number;
  threshold: number;
  inc: () => void;
  isAboveThreshold: () => boolean;
}>((set, get) => ({
  count: 0,
  threshold: 10,
  inc: () => set((s) => ({ count: s.count + 1 })),
  isAboveThreshold: () => get().count > get().threshold,
}));

get vs set 回调的 state 参数

场景 推荐方式 原因
基于当前值计算新值 set((s) => ({...})) 保证拿到最新值
条件判断 / 副作用 get() 不需要修改状态
跨 action 共享逻辑 get() 代码更清晰
const useCartStore = create((set, get) => ({
  items: [],

  addItem: (item) =>
    set((state) => ({
      // 需要在 set 里基于 items 计算 → 用 state 参数
      items: [...state.items, item],
    })),

  checkout: () => {
    // 需要读取多个字段做条件判断 → 用 get()
    const { items } = get();
    if (items.length === 0) {
      alert('Cart is empty!');
      return;
    }
    // ...
  },
}));

subscribe 的完整用法

const useStore = create((set) => ({ count: 0 }));

// 监听所有变化
const unsub1 = useStore.subscribe((state, prevState) => {
  if (state.count !== prevState.count) {
    console.log('count changed');
  }
});

// 配合 subscribeWithSelector 中间件监听单个字段
import { subscribeWithSelector } from 'zustand/middleware';

const useStore2 = create(
  subscribeWithSelector((set) => ({ count: 0, name: 'Alice' }))
);

// 只监听 count 的变化
const unsub2 = useStore2.subscribe(
  (state) => state.count,        // selector
  (count, prevCount) => {        // callback
    console.log(`count: ${prevCount}${count}`);
  },
  { equalityFn: shallow,        // 可选:自定义比较函数
    fireImmediately: true }      // 可选:立即触发一次
);

💡 最佳实践subscribe 的典型场景是日志记录、持久化同步、与外部系统(如 WebSocket、Canvas)的桥接。组件中应始终使用 Hook 选择器而非 subscribe


destroy — 销毁 Store

// 移除所有 listener,清理订阅
useStore.destroy();

典型场景:测试中重置 store,或在微前端/multi-app 场景下卸载模块时清理。


setStategetState — 外部 API

这两个方法在 React 组件外使用(属于 vanilla API):

// 读取状态
const state = useStore.getState();

// 直接设置状态(等价于 store 内部的 set)
useStore.setState({ count: 10 });

// 函数式更新
useStore.setState((prev) => ({ count: prev.count + 1 }));

// 替换整个 state
useStore.setState(initialState, true);

💡 最佳实践setState 是绕过 action 直接修改状态的逃生舱。在测试中用它重置状态,在 WebSocket 等外部回调用它同步数据。但正常业务逻辑应通过 action 修改,保持可追踪性。


Store 组织模式

模式 1:单一巨型 Store(不推荐)

// ❌ 反模式:所有状态堆在一起
const useAppStore = create((set) => ({
  user: null,
  cart: [],
  theme: 'light',
  todos: [],
  // 20+ actions...
}));

模式 2:按功能域拆分(推荐)

// stores/useUserStore.ts
export const useUserStore = create((set) => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// stores/useCartStore.ts
export const useCartStore = create((set, get) => ({
  items: [],
  add: (item) => set((s) => ({ items: [...s.items, item] })),
  total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));

// stores/useThemeStore.ts
export const useThemeStore = create((set) => ({
  theme: 'light' as const,
  toggle: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
}));

💡 最佳实践:Zustand 按功能域拆分 Store(每个 Store 独立),而不是把所有状态塞进一个 Store。这与 Redux 的单一 Store 哲学不同。多 Store 模式天然隔离了状态域,避免了不必要的重渲染。

模式 3:跨 Store 通信

// 一个 Store 的 action 中可以操作另一个 Store
const useCartStore = create((set) => ({
  items: [],
  add: (item) => set((s) => ({ items: [...s.items, item] })),
  clear: () => set({ items: [] }),
}));

const useUserStore = create((set) => ({
  user: null,
  logout: () => {
    set({ user: null });
    // 登出时清空购物车
    useCartStore.getState().clear();
  },
}));

🚨 陷阱:跨 Store 直接调用要留意循环依赖。如果 Store A 依赖 Store B 的 action,且 Store B 也依赖 Store A,在模块初始化时可能出问题。遇到这种情况考虑提取共享逻辑到第三个 Store 或工具函数。


Slice 模式

当功能复杂时,可以将 Store 拆成 slices,每个 slice 是一个独立的 creator 函数:

import { create, StateCreator } from 'zustand';

// ---- slice 1: 熊相关 ----
interface BearSlice {
  bears: number;
  addBear: () => void;
  eatFish: () => void;
}

const createBearSlice: StateCreator<BearSlice> = (set) => ({
  bears: 0,
  addBear: () => set((s) => ({ bears: s.bears + 1 })),
  eatFish: () => set((s) => ({ bears: s.bears + 1 })),
});

// ---- slice 2: 鱼相关 ----
interface FishSlice {
  fishes: number;
  addFish: () => void;
}

const createFishSlice: StateCreator<FishSlice> = (set) => ({
  fishes: 0,
  addFish: () => set((s) => ({ fishes: s.fishes + 1 })),
});

// ---- 组合 ----
interface BoundStore extends BearSlice, FishSlice {}

const useStore = create<BoundStore>()((...args) => ({
  ...createBearSlice(...args),
  ...createFishSlice(...args),
}));

Slice 间相互依赖

// 鱼 slice 需要访问熊的 bears
const createFishSlice: StateCreator<
  BoundStore,        // 完整类型
  [],                // 中间件
  [],                // 无额外依赖
  FishSlice          // 返回类型
> = (set) => ({
  fishes: 0,
  addFish: () => set((s) => ({ fishes: s.fishes + 1 })),
  // 跨 slice 读取
  report: () => {
    const state = get() as BoundStore;   // 需要 as 断言
    console.log(`Bears: ${state.bears}`);
  },
});

💡 最佳实践:Slice 模式适合大型应用中的团队协作(每个 slice 一个文件,各自维护)。小型项目直接分多个独立 Store 就够了,不需要这种复杂度。


createapi 参数

create 的 creator 函数还有第三个参数 api,它暴露了 store 的底层 API:

const useStore = create((set, get, api) => ({
  count: 0,

  // api 就是 store 本身
  // api.getState() === useStore.getState()
  // api.setState(...) === useStore.setState(...)
  // api.subscribe(...) === useStore.subscribe(...)
  // api.destroy() === useStore.destroy()

  // 通常不需要用 api,除非你要在创建 store 时注册副作用
}));

// api 是同一个引用——可以提前暴露出去

大多数场景不需要使用 api 参数,它主要为高级场景(如创建自定义中间件)预留。