Zustand 是一个极简的 React 状态管理库,核心代码约 40 行,基于发布-订阅模式。它和 Redux 解决同一个问题(跨组件共享状态 + 精准重渲染),但去掉了 Redux 的 action/reducer/dispatch 间接层。本页从状态管理的演变出发,梳理 Zustand 的设计动机和内部原理。
概述
为什么需要状态管理
useState 管理组件内部状态足够,但跨组件共享状态时产生 prop drilling 问题——状态要一层层通过不关心它的组件传递。useContext 解决了 prop drilling,但缺乏精准重渲染(Context value 任何字段变化都触发所有消费组件重渲染)和中间件能力(持久化、日志、devtools)。
状态管理库的核心价值:跨组件共享 + selector 精准重渲染 + middleware 生态。
状态管理演变线
| 时期 | 方案 | 解决的问题 | 引入的新问题 |
|---|---|---|---|
| 2014 | Flux | 数据流混乱(双向绑定) | 架构模式无统一实现,生态碎片化 |
| 2015 | Redux | 统一了 Flux 实现,single source of truth | boilerplate 多(action type + creator + reducer) |
| 2017 | Redux Toolkit | 减少 boilerplate | 心智模型仍是 action → dispatch → reducer |
| 2015/2019 | MobX | 自动追踪依赖 | ”魔法”行为不透明,调试不如 action log |
| 2019 | Zustand | 极简 API,无 Provider | 无 action log,调试可追溯性弱 |
| 2020 | Jotai | 原子化状态 | 大量 atom 难以管理全局视图 |
| 2021 | Valtio | Proxy 直接修改 | Proxy 行为有时不直观 |
工作方式
Vanilla Store(与 React 无关)
Zustand 的核心是一个不依赖 React 的可订阅对象:
function createStore(createState) {
let state
const listeners = new Set()
const setState = (partial) => {
state = Object.assign({}, state, partial) // 浅合并
listeners.forEach((l) => l(state)) // 通知订阅者
}
const getState = () => state
const subscribe = (l) => { listeners.add(l); return () => listeners.delete(l) }
state = createState(setState, getState)
return { getState, setState, subscribe }
}
这和 Redux createStore 底层一样是发布-订阅。区别:Redux 更新走 dispatch(action) → reducer,Zustand 直接调 setState(partial)。
不需要 Provider 的原因
Redux 的 React 绑定通过 Context 注入 store 实例,所以需要 <Provider>。Zustand 的 store 是模块作用域的闭包变量(单例),import Hook 就能访问,不需要 Context 传递。
trade-off:不能在同一个应用里用不同 Provider 注入不同 store 实例(微前端场景可能需要)。
React 绑定:useSyncExternalStore
Zustand 的 Hook 内部使用 React 18 的 useSyncExternalStore 连接外部 store 和 React 渲染系统。这个 API 保证在 concurrent mode 下不会发生 tearing(同一次渲染中不同组件读到不同版本的状态)。
Selector 精准重渲染
const locale = useSettingsStore((state) => state.locale)
每次 store 更新,Zustand 调用 selector 取新值,和上次用 Object.is 比较。只有返回值变化才触发重渲染。
陷阱:selector 返回新对象时((s) => ({ a: s.a, b: s.b })),每次都是新引用,导致不必要重渲染。用 useShallow 做逐字段浅比较解决。
set() 的浅合并
set({ locale: 'en' }) 只覆盖 locale,其他字段保留。Redux reducer 必须 return { ...state, locale } 手动展开。但浅合并只作用于第一层——嵌套对象需要手动处理或用 immer middleware。
Middleware 函数组合
create()(
devtools(persist(immer((set) => ({...}))))
)
middleware 是高阶函数的嵌套组合(compose 模式),不是 Redux 的 dispatch 管道拦截器。每层 middleware 增强 set/get 的行为。
与 Redux Toolkit 对比
| 维度 | Redux Toolkit | Zustand |
|---|---|---|
| 更新方式 | dispatch(action) | set(partial) 直接调函数 |
| Provider | 必须 | 不需要 |
| boilerplate | slice + store config + Provider | 1 个 create() 调用 |
| Action 可序列化 | 是,完整 action log | 否 |
| 时间旅行调试 | 一等公民 | devtools middleware 部分支持 |
| 异步模式 | createAsyncThunk, RTK Query | 自行在 action 里写 try-catch |
| 持久化 | 需要 redux-persist + 配置 | 内置 persist middleware |
Redux 更好的场景:大团队需要 action 审计、复杂异步工作流、time-travel debugging、RTK Query 做 server state 管理。
Zustand 更好的场景:中小型应用、状态简单、不需要 action log、追求最少 boilerplate。
Zustand 的诚实缺点
- 无 action log:调用
set()不产生可序列化的 action,状态变更的”历史记录”不如 Redux 完整 - 测试不如 Redux 方便:没有 Provider 意味着不能用不同 Provider 注入 mock store,需要用
store.setState()手动重置 - 大型团队缺乏约束:Redux 的 action/reducer 分离强制了一种代码组织规范,Zustand 的”自由”在大团队里可能导致 store 结构混乱
- 社区和生态不如 Redux 成熟:虽然 npm 下载量已超 Redux,但 Redux 的中间件生态(saga, observable, persist, logger)和教程资源仍然更丰富
版本说明
本页基于 MindGym M1 阶段(Expo SDK 54, Zustand 5.x, 2026-04-13)。
参见
- Zustand
- Redux
- React
- 2026-04-13-Metro Hermes 与 JSI — MMKV + JSI 同步存储的原理
- 2026-04-13-React Native 新架构概览
参考
- Zustand GitHub — 官方文档,完整 API 和 recipes
- React 官方: useSyncExternalStore — Zustand React 绑定的底层 API
- Dan Abramov — You Might Not Need Redux (2016) — Redux 作者论述 Redux 不适合所有场景
- Daishi Kato 博客 — Zustand/Jotai/Valtio 作者的设计哲学