mCell
14 / 14

React 的状态管理哲学:从 Redux 到 Zustand 与 Recoil

从“到底该不该上状态库、该选哪个”这个长期争论出发,用 React 的设计哲学拆解状态管理的本质问题:共享范围、更新频率、可预测性与调试能力;对比 Redux(集中式 + 可追溯)、Zustand(轻量 store + selector)、Recoil(原子依赖图),并给出可落地的选型准则与迁移策略

React 状态管理是一个“越写越像宗教战争”的话题:

  • 有人说:Redux 太重,别用
  • 有人说:不用 Redux 迟早写成屎山
  • 有人说:Zustand 才是现代答案
  • 也有人说:Recoil 的原子模型才是未来

真相是:没有银弹
你不该从“库好不好”出发选型,而应该从一个更本质的问题出发:

你要管理的状态到底是什么?它的生命周期、共享范围、更新频率与调试需求是什么?

这篇我们用“问题驱动”的方式,把 Redux/Zustand/Recoil 放回它们要解决的矛盾里,你会得到一套能长期使用的选型框架。


0. 先把状态分三类:这一步决定你 80% 的架构

你可以把 React 应用里的状态粗分为:

0.1 UI 局部状态(Local UI State)

  • 输入框内容
  • 弹窗开关
  • tab 选中项
  • hover / focus

特点:

  • 生命周期短
  • 只在局部组件树内使用
  • 更新频率高
  • 不需要全局共享

默认方案:useState/useReducer + 组件拆分(最稳)

0.2 模块共享状态(Shared Module State)

  • 当前页面的筛选条件 + 列表数据
  • 一个编辑器模块的选区/面板状态
  • 多个兄弟组件共享的“工作区状态”

特点:

  • 共享范围有限(通常是一个页面或模块)
  • 需要跨层通信,但不一定跨路由
  • 常常伴随副作用(请求、缓存、分页)

默认方案:useReducer + Context(Provider 下沉)(你前面已经学过)

0.3 应用级全局状态(App/Global State)

  • 用户信息、登录态、权限
  • 全局配置、主题、多语言
  • 全局消息队列(toast)
  • 多页面共享的业务实体缓存(如商品、订单、会话)

特点:

  • 生命周期长
  • 共享范围大(跨路由、跨页面)
  • 需要更强的“可控性”(调试、追踪、持久化)
  • 容易出现并发更新与一致性问题

这类状态才是状态库真正的主战场。

记住一句话:不要为了“看起来专业”把 Local/Module 状态塞进全局 store。
全局化是有成本的:心智负担、重渲染、耦合与调试复杂度。


1. Redux 为什么能成为“曾经唯一答案”?它解决的是“可预测性与可追溯性”

Redux 的出现背景可以理解为:

大型前端应用里,状态变化散落在各处,时间线不可追踪,Bug 难复现难定位。

Redux 用三板斧解决:

  1. 单一数据源(single store):状态集中
  2. 纯函数 reducer:变化可预测
  3. action 日志:每一次变化都是可记录的事件

1.1 Redux 的真正价值:时间线与因果可见

当你的系统复杂到:

  • 一个动作会引起多个模块联动
  • Bug 只在某个操作序列出现
  • 你需要“回放现场” 那么 Redux 的 DevTools(时间旅行/日志)是非常强的生产力。

1.2 Redux 的代价:样板与“全局化倾向”

经典 Redux 会让很多人反感:

  • action types
  • action creators
  • reducer 组织
  • connect/useSelector/useDispatch 的样板

Redux Toolkit 很大程度缓解了样板,但 Redux 的哲学仍是:

把变化集中管理,优先可控性与可追溯性。

这也意味着它更适合:

  • 大团队协作
  • 强调可调试与规范
  • 业务状态复杂、联动多的应用

2. Zustand 为什么受欢迎?它解决的是“轻量共享 + 更贴近 React 的使用方式”

Zustand(以“轻量 store”著称)的体验常被形容为:

  • 写起来像 hooks
  • 上手成本低
  • 样板少
  • selector 很好用(只订阅需要的字段,降低重渲染)

2.1 它的核心哲学:小而直接

很多项目的真实需求是:

  • 我确实需要一个跨组件共享的 store
  • 但我不想为此引入一整套 action/reducer 体系
  • 我更希望“像写 hooks 一样写状态”

Zustand 恰好满足:

  • 全局或模块 store 都可以
  • store 里放状态 + 方法
  • 组件按 selector 订阅(减少无效更新)

这使它非常适合:

  • 中小团队快速迭代
  • 需要共享但不想引入重框架
  • 希望尽快获得“跨组件共享 + 性能可控”的平衡

2.2 它的代价:规范与追溯能力需要你自己补

Zustand 可以很自由,但自由也意味着:

  • 你需要自己约束 store 结构
  • 复杂联动时,调试时间线不如 Redux 原生强
  • “随手写在 store 里”的副作用逻辑可能逐渐膨胀

所以 Zustand 更像:

给你一把很锋利的小刀,切得很爽,但切大工程要靠纪律。


3. Recoil 的价值:原子模型 + 依赖图(更像“数据流系统”)

Recoil 的核心思想是:

  • 状态拆成很多“原子”(atom)
  • 派生状态用 selector 表达
  • 组件订阅 atom/selector
  • 形成一张依赖图:谁依赖谁、谁变了会影响谁

3.1 它解决的矛盾:全局 store 的“粗粒度更新”

在 Redux 的“单棵大树”里,即使 useSelector 能局部订阅,你仍然在思维上面对:

  • 一个巨大 state tree
  • reducer 负责很多领域逻辑
  • 派生值要么写 selector,要么组件里算

Recoil 的模型更像:

让状态天然是细粒度的,组件只拿自己关心的“原子”,派生关系由 selector 描述。

这对一些场景很友好:

  • 派生状态多、依赖关系复杂
  • 多个视图共享一部分原子,又各自有派生
  • 希望“像数据流图”一样组织状态

3.2 代价:概念更多、生态与心智成本更高

原子模型看似优雅,但你需要:

  • 把状态拆粒度(拆不好就难维护)
  • 理解 selector 的缓存与依赖更新
  • 接受更“框架化”的模型

所以 Recoil 通常更适合:

  • 状态依赖图很复杂的交互应用
  • 你愿意为模型付出学习成本

4. 选型核心:看这 4 个维度,而不是看“谁更火”

你可以用这四个维度做决策:

4.1 共享范围(Scope)

  • 只在组件内:useState/useReducer
  • 模块内共享:useReducer + Context / 小 store(Zustand)
  • 跨路由共享:状态库(Redux/Zustand/Recoil)

4.2 更新频率(Frequency)

  • 高频(输入、拖拽、实时更新):需要更强的“选择性订阅”
    • Zustand/Recoil 往往更轻松
    • Redux 也能做到,但要写好 selector

4.3 可预测性与约束(Predictability)

  • 需要强规范与一致性:Redux(尤其是大团队)
  • 允许更灵活:Zustand

4.4 调试与可追溯(Debuggability)

  • 需要时间线回放、动作日志:Redux 优势明显
  • 可以接受较弱的时间线:Zustand
  • 更偏依赖图与派生调试:Recoil(但也需要工具与经验)

5. 一个非常实用的“默认选项”(不想纠结时就按这个来)

你可以把它当成一套渐进路线:

  1. 先用 React 原生:useState/useReducer + Context(Provider 下沉)
  2. 当出现明确痛点(跨路由共享、性能瓶颈、调试困难)再引入状态库
  3. 库的选择:
    • 需要强规范 + 时间线:Redux(RTK)
    • 想轻量共享 + selector:Zustand
    • 依赖派生复杂、想原子化:Recoil

这套路线能避免“早早全局化”带来的长期负担。


6. 迁移策略:怎么从“纯 React”平滑走向状态库?

6.1 不要一口吃成胖子:先从“最稳定的全局状态”开始

建议先迁移:

  • auth(用户/权限)
  • theme/i18n
  • 全局消息/弹窗控制

因为它们:

  • 生命周期长
  • 共享范围大
  • 业务耦合相对低

6.2 模块状态尽量模块化(避免 store 巨石化)

就算用状态库,也建议:

  • 按领域/模块拆 store(或拆 slice/atom)
  • 保持边界明确
  • 不要把页面局部状态全部“上交全局”

6.3 保持派生逻辑集中

  • Redux:selector
  • Zustand:selector + memo 化派生
  • Recoil:selector(天然)

避免把派生散落在组件里,导致重复计算与不一致。


7. 常见误区:把“数据请求状态”当成普通状态管理

很多应用里最复杂的状态其实是:

  • 服务器数据缓存
  • 请求去重、取消
  • 过期与重验证
  • 乐观更新

这类问题往往更适合专门的数据请求库(如 React Query/SWR 等)去处理,而不是自己在 Redux/Zustand 里手撸一套缓存系统。

你可以把它当成一个原则:

服务端状态(Server State)与客户端状态(Client State)最好分开治理。
状态库主要管 Client State,数据请求库主要管 Server State。


8. 本文小结:状态管理的“哲学”其实是边界管理

你应该带走的不是“某个库的优劣”,而是一套边界判断:

  1. 先按 Local / Module / Global 分类状态
  2. 共享范围越大、生命周期越长、联动越复杂,就越需要更强的约束与工具
  3. Redux 强在可追溯与规范;Zustand 强在轻量与选择性订阅;Recoil 强在原子依赖图
  4. 不要过早全局化;用痛点驱动引入复杂度
  5. Server State 不要硬塞进状态库,交给专门的数据层工具更合适

留言讨论

Discussion

欢迎交流与反馈