2024 年的 React 状态管理
最佳选择
作者的选择
在我看来,React 状态管理库可以分为三类:
- 基于 Reducer 的:需要调度操作来更新大型集中式状态,通常称为“单一事实来源”。这类方案包括Redux和Zustand。
- 基于原子:将状态拆分成称为原子的微小数据块,可以使用 React Hooks 进行写入和读取。这类方案包括Recoil和Jotai。
- 基于可变的:利用代理创建可变数据源,可以直接写入或被动读取。这类方案的候选者包括MobX和Valtio。
现在我们已经介绍了 React 状态管理库的三大类。让我们深入研究每一种,并探讨每种方法的优缺点。这将帮助你了解哪个库最适合你的项目需求:
1.基于Reducer的库:
尽管人们普遍批评 Redux 过于复杂,但它自创建以来一直是最受欢迎的状态管理库。
+---------------------+
| Actions |
+----------|----------+
|
v
+---------------------+ +---------------------+
| Reducers | | Store |
+----------|----------+ +----------|----------+
| |
v v
+---------------------+ +---------------------+
| State | | Subscriptions |
+---------------------+ +---------------------+
优势:
-
强大的状态机和时间机器。假设您的所有应用程序状态都位于集中式状态中(这种情况很少发生,因为您的组件中可能存在本地状态),则将存在以下公式:
UI = React(state)
。这意味着单个状态值只会导致一个UI,因此您的应用程序在特定状态下的外观将始终相同。如果您在某个地方备份整个状态,然后调度类似 的更改REVERT(pastState) { state = pastState }
,您的UI将恢复,就像它是截取的屏幕截图一样。 -
最佳 DevTools 支持:通过使用显式操作更新状态,DevTools 可以帮助您指出状态变化的内容、时间和方式。您可以想象这就像在应用程序状态中拥有 Git 提交历史记录一样,是不是很酷?
弱点:
- 样板代码:即使对状态进行简单的更改也需要对代码进行大量更改。
- 学习曲线陡峭:虽然核心简单,但仅凭其本身远远不够。要真正掌握 Redux,你应该知道如何将它与其他库(例如 Saga、Thunk、Reselect、Immer 或 Redux Toolkit)结合使用。大多数情况下,我们只是在 Saga 中使用生成器来通过网络获取一些数据,这感觉有些过度。现代 JS 开发人员倾向于每天使用 async/await。
- TypeScript:虽然完全支持 TypeScript,但大多数情况下需要显式输入才能完成操作、reducer、选择器和状态的类型识别。其他方法则直接支持自动类型推断。
2.基于Atom的库:
这种方法不是将整个应用程序的状态放在一个大型的集中式状态中,而是将其拆分成多个原子,每个原子最好尽可能小,例如原始类型或数组和扁平对象等基本数据结构。然后,如果需要,您可以使用选择器将相关状态分组。
+---------------------+
| Atoms (State) |
+----------|----------+
|
v
+---------------------+ +---------------------+
| Selectors (Derived | | RecoilRoot |
| State) | +----------|----------+
+----------|----------+ |
v v
+---------------------+ +---------------------+
| State Snapshot | | React Components |
+---------------------+ +---------------------+
优势:
-
利用 React 功能:由于 Recoil 和 React 均由 Facebook 开发,因此这一点在意料之中。Recoil 与 React 的前沿功能(例如 Suspense、Transition API 和 Hooks)完美兼容。
-
简单易用且可扩展:仅使用原子和选择器,您仍然可以高效地构建大型响应式应用状态,同时对单个状态变更进行细粒度的控制。现在,状态提升非常简单,只需声明一个原子并将
useState
钩子更改为 即可useRecoilState
。 -
TypeScript:作为一名开发者,我关注 DX 就像用户关注 UI 和 UX 一样重要,我发现 React、Recoil 和 TypeScript 是一个绝佳的组合。在我的项目中,大多数情况下类型都是自动推断的。
弱点:
-
DevTools:如果您正在寻找与 Redux DevTools 相当的工具,很遗憾,没有。
-
无法在组件外部使用状态:尽管 Recoil Nexus 是一种解决方法,但这种状态管理库的设计是基于一个(可能是真的)假设,即所有状态的使用都发生在 React 组件内部。
-
尚不稳定:四年过去了,Recoil 的最新版本(v0.7.7)仍然以 0 开头。我希望,当你读到这篇文章的时候,这些信息已经不再重要了。
3.基于可变的库:
提示:“可变”和“不可变”是指数据在创建后如何被更改:
person.age += 1 // mutable
person = { …person, age: person.age + 1 } // immutable
+---------------------+
| Observables |
+----------|----------+
|
v
+---------------------+ +---------------------+
| Computed Values | | Actions |
+----------|----------+ +----------|----------+
| |
v v
+---------------------+ +---------------------+
| Reaction (Derived | | MobX Store |
| Value) | +----------|----------+
+---------------------+ |
v
+---------------------+
| React Components |
+---------------------+
优势:
- 最简单的 API:通过允许状态直接改变,除非您愿意,否则不需要在组件和状态之间放置样板代码。
- 响应性和灵活性:依赖项会在状态发生变化时自动更新。这简化了应用程序逻辑,使其更易于理解。此外,基于代理的方法有助于最大限度地减少不必要的重新渲染。这也意味着流畅的性能和更灵敏的用户体验。
弱点:
- 魔法太多:自动响应式是一把双刃剑。异步更新中的竞争条件可能会导致应用程序状态混乱,并且在复杂的应用程序中调试变更流程可能非常困难。
- DevTools:再次强调,在我看来,没有任何替代方案能像基于 Reducer 的方法那样提供最好的工具支持。
- 离散 DX:虽然 React 详细阐述了“不可变”方法,但在我的项目中混合使用“可变”数据有时会让我对如何更改数据感到不安全。
最佳选择
再次强调,最适合你项目的 React 状态管理库取决于你和你团队的具体需求和专业知识。请不要:
-
仅根据项目规模和复杂程度来选择库。你可能听说过,X 更适合大型项目,而 Y 更适合小型项目。库的作者在设计库时就考虑到了可扩展性,而项目的可扩展性取决于你如何编写代码和使用库,而不是你选择使用哪个库。
-
将你从一个库学到的最佳实践应用到另一个库。将整个应用程序状态放在一个 Recoil 原子中以实现“单一事实来源”只会导致状态更新困难和性能问题。此外,在 Redux 中将操作定义为 setter 并分派多个操作,而不是在一次提交中批量更改。
作者的选择
TL;DR:Jotai。
我个人更喜欢原子库,因为它有上面列出的优势,而且我之前用 处理异步数据获取和批量加载 UI 时,DX 体验非常流畅<Suspense>
。Jotai 比 Recoil 强的地方在于:
- 无需密钥。命名很麻烦,而且大多数情况下,您不会使用 Recoil 的密钥。既然库可以自动为您获取密钥,为什么还要花时间声明它们呢?这是Recoil 的答案;然而,正如您所见,人们对此并不十分信服。
- 性能。一张图片胜过千言万语,我有 4 张图片:
你可能会认为约 20KB 的大小差异无关紧要,但让我们来看看在一台非常老旧的 Android 设备上进行的基准测试,其中的缓慢表现得非常明显,就像用红色斜条纹填充的条形图一样。正如你所见,Jotai 的内部逻辑需要更少的整体计算,这使得我的应用程序的 LCP(一个重要的 Core Web Vitals 指标)从约 2.6 秒提升到了约 1.2 秒。尽管如此,这种比较可能没有考虑到 Recoil 在其他方面比 Jotai 做得更好的因素(事实上,这是我在这方面的知识范围)。我只想说,Jotai 团队在这方面做得非常出色。
我希望这有帮助!
文章来源:https://dev.to/nguyenhongphat0/react-state-management-in-2024-5e7l