专栏文章在平台上断更有一段时间了,但在这几个月里,团队内关于技术的讨论并没有停滞,本篇便是源于数月前组内一位同学推动的一系列技术分享。话题的起因,是周会上大家对于目前项目中使用Redux优劣的一次“愉快探讨”,进而衍生为对React目前主流状态管理库的一轮Spike。最近讨论告一段落,笔者会通过本篇及之后的几篇文章,剪断截儿说,和大家一起漫谈React状态管理实践。
1. 为什么需要状态管理?
在我们讨论具体的状态管理库优劣之前,先来聊一聊这个问题:为什么在使用React时,需要状态管理?
这里推荐一篇Kent C. Dodds的博文:《Application State Management with React》[1]。重点总结:React是一个component内置状态的库,component可以很轻松地创建和管理自身内部的状态,同时在外部通过props来传递状态。但是随着项目复杂度的提升,props的传递随之变得越来越复杂,即会出现 Prop Drilling[2]的问题:
Prop Drilling is the process by which you pass data from one part of the React Component tree to another by going through other parts that do not need the data but only help in passing it around.
Prop Drilling 简而言之是指:当一个组件自身不需要这个prop,但为了使子组件能获取到这个prop,而不得已去传递这个prop的行为。
那么,Prop Drilling 的出现是好是坏呢?
• 优点:显式优于隐式
Prop Drilling 通过显式传递的方式,在一定程度上避免了过度使用全局变量造成的数据模型混乱。全局定义会导致变量难以追踪,因而当我们想要更新、删除某一个变量时,很难确定该变量是否正在被使用,莽撞的修改很可能造成预期之外的某个组件受到影响。
• 缺点:重构艰难
随着项目复杂度的增加,往往会出现某个prop的传递,穿透多个组件层级的应用场景,因而代码追溯长度也会不断增加,进而导致对component的性能分析异常困难,尤其是在进行代码重构时,积攒的负面影响便会爆发形成较大的阻碍。
再回到前文的问题,状态管理的存在,其中一个主要原因正是为了解决 Prop Drilling 的问题。
React官方也提供了一些方案来处理 Prop Drilling,如 Context API 以及 Component Composition,但不得不承认,Redux 仍是时下最为流行的状态管理库之一,其通过一整套完善的全局store管理方案,帮助开发人应对 Prop Drilling 所导致的痛点。
2. 使用Redux会有什么问题?
全局定义store混乱
实际开发中,无论是刚刚引入Redux的小型项目,还是由多个团队协同开发的大型项目,都会遭遇同一个问题:如何制定出一套明确的策略,从而界定哪些数据应该置于全局的store实现共享,而哪些数据应该避免。例如,我们前端团队曾经一段时间主导理念是追求数据流向的正确性,因而把所有的state全都放在了Redux store中,在这种实现下,Redux成为了我们项目中一个架构级别的层级,用于向UI传输数据。然则,这样做其实有悖于Redux文档[3]中的第3条建议:
Here are some suggestions on when it makes sense to use Redux
1. You have reasonable amounts of data changing over time
2. You need a single source of truth for your state
3. You find that keeping all your state in a top-level component is no longer sufficient
这个问题并不算罕见,以Modal状态的管理为例:某个父页面中,存在两个可切换的子Tab页,而这两个子Tab页中,又都有一个调用相同样式Edit Modal的按钮,因而顺理成章会复用该Edit Modal,但若在父页面组件中管理Modal的props,实现会比较复杂;然而,若交由Redux又会略显大材小用,每次对Modal状态的简单更改都需要铺陈不少无谓的代码。
同时,另一方面,随着项目内页面的增多,显示所需的数据模型也在不断增多,不设限的状态增加必然会导致全局store树的结构越来越深,以至于难以维护。几经周折,开发者甚至无法确定几周前定义的一个store是不是还在以它原本定义的职能运行。
组件复用困难
Redux是一个典型的非分形架构[4]状态库。
A unidirectional architecture is said to be fractal if subcomponents are structured in the same way as the whole is.
在分型架构下,每一个子组件都能够以同样的结构,作为一个应用使用。而在Redux中,其实现方式完全依赖于脱离了组件、处于组件树顶端的全局store。且对状态的管理是与组件connect在一起的,因而脱离Redux store后的组件无法作为一个完整的应用独立运行。
缺少副作用管理
有一种说法是,redux的设计思想就是不产生副作用,然而在实际开发时,面对如今越来越复杂的单页面应用,副作用终归还是需要处理的。
Redux官方并没有提供处理副作用的解决方案,但放出了中间件机制,随之而来社区中出现了不少优秀的中间件,如redux-thunk、redux-promise、redux-saga等,问题勉强算是解决了。
模板代码过多
表面上简单的一处应用改动,不得不修改多处文件才能完成。模板代码若作为优点,可以解释为“保证了代码结构的清晰和严谨”。但作为缺点,想要在这种分散且相互关联的基础上进行修改,着实让开发者头疼。同时,模板代码过多也会导致数据流向变得越发复杂,Redux Toolkit的产生,一定程度上就是为了解决这个问题。
3. 还有什么选择?
除了Redux之外,要在React中实现状态管理,选项还有很多,我们不妨挑选几个主流方案作为示例,稍作对比分析:
-
独立状态管理库:该类方案更关注状态管理的实现方式,以落地一套完整方案,完全接管React的状态管理,如:
Mobx
Recoil
-
基于Hooks:该类方案认为React本身在状态管理方面已经足够或非常出色,唯一缺少的只是如何轻松地共享状态和逻辑,如:
constate
unstated-next
下一篇文章,我们将结合以上几个库的实现思路,从以下几个方面进行分析:
-
是否能满足一个状态管理库最基本的需求:
共享状态
异步处理
-
是否针对现有痛点有所改进:
更细粒度的局部状态管理
较少的模板代码
良好的TS支持
References:
[1] Application State Management with React - Kent C. Dodds
[2] Prop Drilling - Kent C. Dodds
[3] Should You Use Redux? - Getting Started with Redux
[4] Unidirectional User Interface Architectures - André Staltz
“卓派前端工作志,聚焦实用前端技术,让编程更有趣!”
前端技术组 @ 西安卓派科技 NEXT Trucking —拉勾|Boss|知乎|掘金|
如果觉得本文对你有帮助的话,快来关注我们吧!