Zustand 全局状态管理
Zustand 的核心用法:create + persist 实现全局状态、选择性订阅优化性能、与 TanStack Query 的分工边界。
#type / howto
#status / growing
#tech / dev / frontend
#resource / react
[!info] related notes
- 并列: TanStack Query 服务端状态
- 实践: BodySense 项目 MOC
Zustand 全局状态管理
核心问题
React 应用中有两类状态:客户端状态(UI 开关、表单、认证 token)和服务端状态(API 返回的数据)。客户端状态需要一个全局共享、可持久化的方案。
为什么选 Zustand
| 维度 | Redux | Context | Zustand |
|---|---|---|---|
| 模板代码 | 多 | 少 | 极少 |
| 性能 | 好 | 差(全量重渲染) | 好 |
| 持久化 | 需要插件 | 手动 | 内置 persist |
| 学习曲线 | 高 | 低 | 极低 |
Zustand 的核心卖点:一个 create 函数搞定,没有 Provider 包裹,选择性订阅。
最小用法
const useAuthStore = create<AuthState>()((set, get) => ({
user: null,
accessToken: null,
isAuthenticated: false,
login: async (email, password) => {
const data = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) });
set({ accessToken: data.access_token, isAuthenticated: true });
},
logout: () => set({ user: null, accessToken: null, isAuthenticated: false }),
}));
组件中:
const user = useAuthStore((state) => state.user); // 选择性订阅
const login = useAuthStore((state) => state.login); // 方法引用不变,不触发重渲染
持久化
const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({ ... }),
{
name: 'auth-storage', // localStorage key
partialize: (state) => ({
accessToken: state.accessToken,
refreshToken: state.refreshToken,
isAuthenticated: state.isAuthenticated,
user: state.user,
// 不持久化 isLoading、error 等临时状态
}),
},
),
);
选择性订阅
// ✅ 只订阅需要的字段
const user = useAuthStore((state) => state.user);
// ❌ 订阅整个 store,任何字段变化都重渲染
const store = useAuthStore();
与 TanStack Query 的分工
| 维度 | Zustand | TanStack Query |
|---|---|---|
| 数据来源 | 客户端产生 | 服务端获取 |
| 例子 | 认证 token、UI 开关、表单草稿 | 用户列表、会话数据、评估报告 |
| 缓存 | 无(手动管理) | 自动缓存 + 失效 |
| 持久化 | persist 中间件 | 无 |
简单判断:需要持久化到 localStorage → Zustand;来自 API → TanStack Query。
常见错误
在组件外用 hook
// ❌ 组件外不能用 hook
const token = useAuthStore((s) => s.accessToken);
// ✅ 用 getState()
const { accessToken } = useAuthStore.getState();
store 里做异步操作不设 loading
// ❌ 没有 loading 状态
login: async (email, password) => {
const data = await fetch(...);
set({ accessToken: data.access_token });
}
// ✅ 设 loading 和 error
login: async (email, password) => {
set({ isLoading: true, error: null });
try {
const data = await fetch(...);
set({ accessToken: data.access_token, isLoading: false });
} catch (e) {
set({ error: e.message, isLoading: false });
}
}