开源项目
pmndrs/zustand avatar
pmndrs/zustand

Zustand 评测:一个不给你添乱的 React 状态管理库

承担React中状态管理的需要。如果你想构造一个内部有多个state-picks的单个对象,类似于redux的mapStateToProps,你可以使用useShallow来防止当选择器输出不根据shallow equal改变时不必要的重新渲染。

58,679 个 Star2,187 个 ForkTypeScriptMIT

秒懂

它是什么?
Zustand 是一个基于 hooks 的轻量状态管理库,用简化 Flux 原则实现。它没有 Provider 包裹,API 简单,但需要你注意浅比较和覆盖写入等细节。
适合谁用?
Zustand 适合那些不想引入 Redux 样板代码、又觉得 Context 渲染控制不够精细的 React 开发者。如果你需要极小的包体积、直接通过 hooks 消费状态,并且愿意自己管理状态更新的不可变性,Zustand 是一个务实的选择。
能商用吗?
可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
还在维护吗?
在维护。仓库最近一次提交在 5 天前。
用什么语言写的?
主要是 TypeScript(依据 GitHub 的语言统计)。

以上回答依据项目的 GitHub 数据(最近同步于 2026年9月14日)和我们的分析,不构成法律意见。

开源项目深度解析

它解决什么问题,谁该用它

Zustand 解决的是 React 应用中状态管理的两个痛点:Context 的样板代码和渲染性能问题,以及 Redux 的过度设计。文档明确列出它相对 Context 的优势:更少样板、仅在状态变化时渲染组件、集中式且基于 action 的管理。相对 Redux,它简单且不固执己见,hooks 是消费状态的主要方式,不需要用 Provider 包裹整个应用。这适合中小型项目,也适合那些不想被 Redux 的 action type、reducer 和 dispatch 流程束缚的团队。但如果你需要严格的时间旅行调试或团队需要强制的状态流规范,Zustand 的自由风格可能不够。

核心机制:store 即 hook,set 即合并

Zustand 的核心是 create 函数,它返回一个 hook,这个 hook 本身就是一个 store。你可以把任何东西放进去:原始值、对象、函数。状态更新必须不可变,但 set 函数默认会做浅合并,这减轻了手动展开 state 的负担。例如 create((set) => ({ bears: 0, increase: () => set((state) => ({ bears: state.bears + 1 })) })),调用 increase 时,set 接收一个函数,该函数返回一个部分状态对象,Zustand 会将其与现有状态合并。这种设计让 action 定义和状态放在同一个闭包里,不需要额外的 action creator。但要注意,set 的第二个参数设为 true 时会完全替换状态模型,这会清空所有 action,文档特别警告不要误用。

渲染控制:从严格相等到 useShallow

默认情况下,Zustand 的 selector 使用严格相等(old === new)来检测变化,这适合原子状态选择,比如 useBearStore((state) => state.bears)。但如果你想要一次选择多个状态切片,比如构造一个对象 { nuts, honey },每次渲染都会生成新对象,严格相等永远不成立,导致不必要的重渲染。这时可以使用 useShallow 包装 selector,它用浅比较来判断输出是否变化。文档给出了对象、数组和映射选择的示例,比如 useBearStore(useShallow((state) => ({ nuts: state.nuts, honey: state.honey })))。对于更细粒度的控制,你可以提供任何自定义相等函数,但需要改用 createWithEqualityFn,而不是默认的 create,这是 v5 迁移的一个关键点。

异步与外部读写:set 不关心你是否 await

异步 action 在 Zustand 中非常直接。你只需要在 action 函数里等待异步操作完成,然后调用 set 更新状态。文档示例是 fetch 函数:async (pond) => { const response = await fetch(pond); set({ fishies: await response.json() }) }。Zustand 不关心 action 是否异步,它只负责在 set 被调用时更新状态。另外,你可以在组件外部通过 getState、setState 和 subscribe 来读写状态和监听变化。subscribe 会同步触发所有监听器,并且返回一个取消订阅函数。但文档警告,这种技术不推荐用于 React Server Components(如 Next.js 13 以上),因为它可能导致意外 bug 和用户隐私问题。如果你需要在 subscribe 中使用 selector,可以启用 subscribeWithSelector 中间件,它允许你传递 selector、callback 和可选配置(如 equalityFn 和 fireImmediately)。

一个真实的限制:覆盖写入会误删 action

Zustand 的 set 默认合并状态,但如果你需要替换整个状态,可以传递第二个参数 true。文档给出了 deleteEverything: () => set({}, true) 的例子,这会清空整个 store,包括 action。这是一个危险的操作,因为如果你不小心在某个 action 里调用它,你会丢失所有方法,导致组件崩溃。另一个场景是 deleteTuna: () => set(({ tuna, ...rest }) => rest, true),这需要手动排除要删除的字段。这种设计给了你灵活性,但要求你非常清楚当前状态的结构。如果你习惯 Redux 的不可变更新约定,可能觉得这种覆盖方式太原始。此外,如果你使用 TypeScript,文档提醒要查看专门的 TypeScript Usage 部分,因为类型推导可能在某些场景下需要额外配置。

替代方案:Redux 和 Context 的取舍

Zustand 的主要替代是 Redux 和 React 自带的 Context。Redux 采用严格的单向数据流,通过 action type、reducer 和 dispatch 强制状态变更的可预测性,但样板代码多,且需要 Provider 包裹。Zustand 则不需要 Provider,直接通过 hook 消费状态,更简洁。Context 的问题在于,当上下文值变化时,所有消费该上下文的组件都会重渲染,除非你手动拆分 Provider 或使用 memo。Zustand 通过 selector 机制,只让依赖特定状态切片的组件重渲染。但要注意,Zustand 的灵活性意味着你需要自己管理状态结构,而 Redux 的规范能防止团队随意修改状态。如果你需要中间件生态,Redux 有更成熟的 devtools 和持久化方案,Zustand 则主要依赖官方提供的 subscribeWithSelector 等中间件。

维护与升级成本:MIT 许可下的务实选择

Zustand 采用 MIT 许可证,可以自由使用和修改。项目最近活跃,v5.0.15 在 2026 年 8 月发布,说明维护持续进行。升级到 v5 时,你需要关注迁移指南,特别是自定义相等函数需要改用 createWithEqualityFn,这是文档明确提到的破坏性变更。如果你从 v4 升级,检查代码中所有使用 create 的地方,看是否依赖了旧版的默认行为。由于 Zustand 的 API 小而稳定,升级成本通常低于 Redux 这类大型库。但要注意,它依赖 React 的并发特性,文档提到它处理了 zombie child 问题和 context loss,这意味着你最好保持 React 版本较新,否则可能遇到兼容性问题。总体而言,维护成本低,但你需要定期关注版本更新,因为 v5 的迁移不是完全无痛的。

编辑结论

Zustand 适合那些不想引入 Redux 样板代码、又觉得 Context 渲染控制不够精细的 React 开发者。如果你需要极小的包体积、直接通过 hooks 消费状态,并且愿意自己管理状态更新的不可变性,Zustand 是一个务实的选择。但如果你依赖 Redux 的 devtools 生态或需要严格的状态流约束,它可能会让你觉得太自由。在采用之前,先确认你的项目是否涉及 React Server Components,因为文档明确警告在 RSC 中使用 getState 和 subscribe 可能导致意外 bug 和隐私问题。另外,如果你需要自定义相等函数来控制渲染,记得使用 createWithEqualityFn 而不是默认的 create。最后,检查你的 TypeScript 版本是否与 v5 兼容,因为迁移到 v5 时有一些破坏性变更。

官方来源

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
社区笔记

社区笔记