Zustand 状态管理最佳实践

Created on

前言

Zustand 轻量、自由,但自由意味着需要更强的工程纪律。本文从结构设计、性能订阅、异步治理与可测试性几个维度总结实践要点。

1. 只管理真正的全局状态

跨页面或多组件共享的状态适合放在 store,如用户信息、购物车、权限、主题。组件局部 UI(输入框、折叠、临时弹窗)仍然建议用 useState

2. 按领域拆分 store

避免单一巨型 store,按业务域拆分能降低耦合、提升可维护性。

3. state 结构保持扁平

深层嵌套会导致更新复杂、选择器冗长。优先扁平化结构,必要时使用 id 映射。

type UserMap = Record<string, User>;

4. selector 驱动渲染

避免直接使用 useStore() 读取整个 state,使用 selector 精确订阅。

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

多个字段时配合 shallow 减少重渲染:

import { shallow } from "zustand/shallow";

const { user, isLoading } = useAuthStore(
  (s) => ({ user: s.user, isLoading: s.isLoading }),
  shallow
);

5. actions 承载业务逻辑

组件只调用 action,业务流程集中在 store 内部,更易维护与测试。

const useAuthStore = create<AuthState>((set) => ({
  user: null,
  isLoading: false,
  login: async (params) => {
    set({ isLoading: true });
    const user = await api.login(params);
    set({ user, isLoading: false });
  },
}));

6. 异步状态集中治理

统一维护 loading/error 避免散落在组件。

type AuthState = {
  user: User | null;
  isLoading: boolean;
  error: string | null;
};

7. action 避免读取外部可变数据

不要在 action 内部直接读取外部可变变量,通过参数传递保证可预测性。

login: async (email, password) => {
  const user = await api.login({ email, password });
  set({ user });
};

8. subscribeWithSelector 做副作用

当需要在状态变化时执行副作用(如写入缓存),用订阅替代组件内监听。

const useStore = create(
  subscribeWithSelector((set) => ({
    token: null,
    setToken: (token) => set({ token }),
  }))
);

useStore.subscribe(
  (s) => s.token,
  (token) => {
    if (token) localStorage.setItem("token", token);
  }
);

9. 持久化克制使用

persist 只保存必要字段,避免存大型对象或敏感信息。

persist((set) => ({ token: null, setToken: (t) => set({ token: t }) }), {
  name: "auth",
  partialize: (s) => ({ token: s.token }),
});

10. immer 降低复杂更新成本

嵌套数据更新复杂时,使用 zustand/middleware/immer 提升可读性。

import { immer } from "zustand/middleware/immer";

const useStore = create(
  immer((set) => ({
    todos: [],
    toggle: (id) =>
      set((s) => {
        const item = s.todos.find((t) => t.id === id);
        if (item) item.done = !item.done;
      }),
  }))
);

11. action 设计可测试

请求层抽离,action 接收依赖,测试时可 mock。

login: async (params, api = authApi) => {
  const user = await api.login(params);
  set({ user });
};

12. 避免过度动态化

不要为了灵活在运行期动态创建过多 store,会削弱可追踪性。多数情况下静态定义 + 按需组合更可靠。

结语

Zustand 的关键在于边界与纪律。控制 store 范围、合理拆分、精确订阅、集中 action 逻辑,就能构建稳定可维护的状态体系。