在我的认知中, React 状态管理库可以分为三类:
- 基于事件派发 : 需要调度
Action
来更新状态,通常称为"单一数据源"。在这个组中,我们有Redux
和Zustand
。 - 基于原子化 : 将状态分割成称为原子的微小数据块,可以使用 React hooks 对其进行写入和读取。在这个小组中,我们有
Recoil
和Jotai
。 - 基于可变数据 : 通过代理/劫持操作来创建可变数据源,该数据源实现了直接写入或被动读取的逻辑。该组的候选者是
MobX
和Valtio
。
现在我们已经介绍了 React 状态管理库的三个主要类别。让我们更深入地研究每种方法并探讨每种方法的优点和缺点。这将帮助您了解哪个库最适合您的项目需求:
基于事件派发
尽管人们普遍批评 Redux 过于复杂,但它自创建以来一直是最受欢迎的状态管理库。
优势:
-
强大的状态机和时间机。假设应用程序所有状态都集中位于
State
状态内(这种情况很少发生,因为组件中可能有本地状态),则存在以下公式:UI = React(State)
。这意味着单个状态值只会产生一个 UI,因此您的应用程序在特定状态下看起来始终相同。如果您在某处备份整个状态,然后发送类似 REVERT(pastState) { state = pastState } 的更改,您的 UI 将被恢复,就像它是捕获的屏幕截图一样。 -
最好的 DevTools 支持:通过使用显式操作更新状态,DevTools 可以帮助您指出状态更改的内容、时间和方式。你可以想象它就像在你的应用程序状态中拥有一个 Git 提交历史记录,这有多酷?
劣势:
-
代码冗余:即使对状态进行简单的更改也需要对代码进行大量更改。
-
陡峭的学习曲线:虽然其核心很简单,但仅靠它自己是远远不够的。要真正掌握 Redux,您应该知道如何将它与其他库(例如 Saga、Thunk、Reselect、Immer 或 Redux Toolkit)一起使用。老实说,当我大部分时间在 Saga 中编写生成器时,我只是想通过网络获取一些数据,这感觉有点矫枉过正。我们现代 JS 开发人员倾向于每天使用 async/await。
备注: (Saga、Thunk 都用于帮助开发者更方便地处理异步数据,具体细节可查看 juejin.cn/post/710530...)
-
TypeScript:虽然完全支持 TypeScript,但需要显式地定义好数据类型,这将耗费开发者大量时间。而其他方法直接支持自动类型推断。
基于原子化
这种方法不是将整个应用程序状态放入一个集中式State
中,而是将其拆分为多个原子,每个原子最好像原始类型或基本数据结构(如同基本数据类型与引用数据类型之间的关系一样)一样小。然后,如果需要,您可以稍后使用选择器将相关状态分组在一起。
优势:
- 利用 React 功能:这是预期的,因为 Recoil 和 React 都是由 Facebook 创建的。 Recoil 与 Suspense、Transition API 和 Hooks 等尖端 React 功能配合得很好。
- 简单且可扩展:通过仅使用原子和选择器,您仍然可以有效地构建巨大的反应式应用程序状态,同时对各个状态更改进行细粒度控制。现在,提升状态就像声明一个原子并将 useState 挂钩更改为 useRecoilState 一样简单。
- TypeScript:作为一名关心 DX(开发体验) 的开发人员,就像用户关心 UI(用户界面) 和 UX(用户体验) 一样,我发现 React、Recoil 和 TypeScript 是一个美妙的组合。在我的项目中,大多数时候类型都是自动推断的。
劣势:
- 没有配套的 Devtools.
- 无法在组件之外使用状态.
基于可变数据
提示:"可变"和"不可变"是指数据创建后的更改行为。
person.age += 1 // 可变`
person = { ...person, age: person.age + 1 } // 不可变
优势:
- 最简单的 API:通过允许直接改变状态,组件和状态之间不需要额外的代码,除非您愿意这样做。
- 反应性和灵活性:只要状态发生变化,依赖关系就会自动更新。这简化了您的应用程序逻辑并使其更易于理解。此外,基于代理的方法有助于最大限度地减少不必要的重新渲染。这也意味着流畅的性能和更灵敏的用户体验。
劣势:
- 魔法太多:自动反应(即基于可变数据的更新行为)是一把双刃剑。异步更新中的竞争条件可能会导致应用程序状态混乱,并且在复杂的应用程序中调试更改流程可能具有挑战性。
- DevTools:没有类似 Dedux 一样的 Devtools.
- 不一致的 DX(开发者体验):React 本身的更新机制是基于"不可变"数据更新,在我的项目中混合"可变"数据更新行为有时会让我对更改数据感到不安全。
最佳选择
同样,最适合您的项目的 React 状态管理库取决于您和您的团队的特定需求和专业知识。请不要:
-
仅根据项目规模和复杂性选择库。因为,您可能在某处听说过 X 更适合大型项目,而 Y 更适合小型项目。库作者在设计他们的库时考虑到了可扩展性,而项目的可扩展性取决于您编写代码和使用库的方式,而不是您选择使用哪些库。
-
将您从一个 library 学到的最佳实践应用到另一个 library。将整个应用程序状态放入单个 Recoil 原子中以实现"单一事实来源"只会导致状态更新和性能问题。就像在 Redux 中定义 action 一样,在 set 过程中 dispatch 多个 action,而不是在一次提交中批量更改。
作者的选择 - jotai
我个人更喜欢原子库,因为上面列出的优点以及我在处理异步数据获取(其实 zustand 不会有这个问题)和使用 批量加载 UI (这一块确实是原子化的优势) 时的无痛 DX(开发者体验)。 Jotai比Recoil做得更好的是:
不需要key
。命名这件事很困难,而且大多数时候,您不会使用 Recoil 的 key 。那么,当库可以自动为您提供key 时,为什么还要花时间声明它们呢?这是 Recoil 的回答;然而,正如你所看到的,人们并不十分认可。
看看性能表现。一图胜千字,而我,有4个:
Recoil
- 包体积:
- LCP:
Jotai
- 包体积:
- LCP:
您可能会说,大约 20Kb 的大小差异并不重要,但让我们看一下在非常旧的 Android 设备上进行的基准测试,其中缓慢现象与填充对角红色条纹图案的条形一样明显。正如您所看到的,Jotai 内部逻辑需要较少的整体计算,这将我的应用程序的 LCP(一项重要的 Core Web Vitals 指标)从约 2.6 秒提高到约 1.2 秒。尽管如此,这种比较可能没有考虑到 Recoil 比 Jotai 做得更好的其他因素(事实上,我的知识仅限于此)。我只想说Jotai团队在那里做得非常出色。
我希望这对你有帮助!
相关文章推荐:
# 【前端状态管理】React 状态管理工具如何选 context/redux/mobx/zustand/jotai/recoil/valtio