:::info ℹ️ 随记仅作参考之用,可能写作有不严谨的地方,还请理解。 :::
前端的应用状态管理的方式有很多,早期曾经梳理过 状态管理与前端应用程序结构 的设计,但在 React 实践之中依旧会不断发现新的问题,借着近期的实践做一个随笔,记录对应用最佳状态管理方式的寻找过程。
一般说来,一个前端的应用的状态可以分为 👇 几种类型:
- 容器状态:这类一般不受控,是一种只读的状态,或者通过消息事件监听 or 回调模式触发变更的状态。比如:
window.history
/window.location.hash
等等,这类状态的特点是变更的代码一般不受我们直接控制。 - 服务端数据或状态:存储在服务端的具备状态的数据或状态,一般状态的变更是发生在服务端,并在服务端做持久化的。这类状态大量在前端应用中以展示形态数据呈现,经常需要前端开辟变量做管理。尤其在中后台系统中,表格就是典型的视图载体,而 数据的形态往往直接和服务端结构相关。也包含缓存在本地,未来需要持续和服务端同步的数据和状态。
- 客户端视图状态:这类往往比较多地存在于客户端本地,用于纯粹表示前端的视图结构、交互状态的。细化下去又可以表示为:应用级共享的、页面级共享 的和 组件级共享 的。
为了更好地解决上述状态的管理,我们可以巧妙地使用并设计一些「使用原则」、「设计策略」,来让我们的应用更好地使用现成的技术做状态管理。
设计原则
- 客户端共享状态应该根据作用范围做分级:从下往上依次是:组件级 => 页面级 => 应用级。对于存在大量客户端中间状态的应用(重客户端的应用),比如:文本编辑器(TextEditor)等,就需要特殊的客户端状态表示模型或者数据模型去做精细化承载。常见的比较适合做客户端状态管理的工具库:
React
提供了基础的组件内部管理自身状态的机制,若非 共享状态,请尽量使用 React 提供的状态管理,useState
进行操作,使用React.Context
做静态数据共享。Redux
:单一全局 Store,可以表示为应用级 Store,使用 Flux 的动作事件流去做状态的可视化管理,是目前比较主流的模式,该模式对状态数据量偏小的场景非常适合,但数据量变大后,全局 Store 臃肿就是灾难性地管理,虽然可以切割但是维护的成本会越来越高。可以通过 SpilitReducer 来对全局 Store 做拆解,分解出页面级和组件级。目前比较流行的是 ReduxToolkit 规定的使用方式,去做状态管理。Mobx
/Recoil
:比较主流的多 Store,或者多 State 管理,可以表示为应用级、页面级、组件级。这种比较扁平,状态可以和视图一对多,而且Recoil
天然对 Hook 友好,毕竟是 React 团队的亲儿子。这类库早期状态维护简单的时候,很舒服,可以充分使用 Reactive 的响应式能力,配合@compute
特性,依赖网和数据变更维护很轻松。但是随着发展或者使用不慎,往往会加大理解的复杂度,特别是多人维护的场景,很容易把状态管理越弄越乱,缺乏明确的规则和设计指引的团队和业务需要慎用这种非集中式管理的工具库。Rx.js
RX 不算状态管理库,但确是多异步流数据处理的神器,这里提到它主要是想表明它可以处理类似于像 IM 这种应用,多渠道消息流合并处理渲染的问题,对多人协作的场景,多实时数据产生和处理也比较好的工具,唯独就是有入门成本,需要专业性比较强的团队才可以驾驭。
- 客户端共享的状态应该越少越好:不可否认的是共享的状态会增加设计和理解的认知负荷,任何应用都应该减少上层的共享状态的数量,尽可能将状态变化控制在更小的粒度上。
- 关注共享状态的可视化:
Redux
提供了一套 logger 工具,在 Console 和 DevTool 中都可以看到每一次事件 dispatch 提供的状态变化,Recoil
的 snapshot 也可以做 state diff。总而言之,为了提升 debug 的效率,状态 diff,包括状态 diff 的原因(Action)提供是非常有助于开发者提升复杂应用的 debug 能力。 - 容器类状态可以做简单映射封装:对于 Web 容器上的 hash 路径或者 pathname 往往会携带状态信息,我们需要在应用级或者页面级建立一个状态关系的映射,便于不同业务和代码共享读取。
ReactRouter
:使用三方库做状态封装也是可取的。Router 就封装了外部容器的路由状态,做统一管控。
- 服务端的数据和状态的封装按需分级,也可以做中心化管理:对于少量服务端数据的场景来说,存储在组件 or 页面级别的状态中是合理的,但有大量请求关联大量的状态和数据的可以使用一个集中管理的状态机制,将请求和服务端状态表示为前端的对象客体(类似 ReactQuery 的模式)进行封装,甚至虚拟为一个本地 I/O 的数据库先做离线化的数据读写,再同步服务端,这种模式按需选取。
ReactQuery
:React Query 最佳实践 提供了一种将服务端状态和客户端状态做连接的很好的实践。很多时候我们发现对于一些中后台系统剥离了服务端状态,客户端状态寥寥无几。ReactQuery
通过对请求和请求数据、过程的封装抽象,将服务端数据和客户端数据状态做了隔离,是一种对前后端数据状态做分离管理比较好的范式。可以降低前端应用设计的代码的复杂度。