第十一节 React状态管理
一、前言
- 为什么不直接使用windows对象:
- 全局污染,重复声明;
- 直接取值和赋值,数据变更不清晰;
- 渲染粒度无法控制;
- 无法进行时间旅行。
二、什么是状态管理
01. React出现后
- 前端组件化方案深入人心;
- React遵循的是单向数据流的原则,属性通过Props自上而下传递;
- 当页面比较简单,组件之间层级关系比较浅的时候,这种自上而下的单项数据流的方法是不会有问题的;
- 当页面相对复杂,组件的嵌套层级变深,这种单向数据流的传递方式会导致“嵌套地狱”;
- 状态管理本身就是用于解决“嵌套地狱”的问题,解决跨层组件之间的数据通信和状态共享;
02. 状态管理工具
-
本质:管理共享内存中的状态;
-
包含:
- 共享内存;
- 管理状态;
- 页面通信;
- 组件通信;
- 刷新失效;
-
详细定义:单页应用的各个组件本身是共享内存的,如果将状态保存在内存中,就可以读写统一内存中的变量,从而达到状态共享的目的;
03. 为什么React有这么多状态管理工具
-
Vue:Vue(Pinia);
-
Angular:Service 和 Rxjs;
-
React:Flux、Redux、Mobx、Rxjs、Recoil、Jotai、Zustand;
-
与不同前端框架定义有关,Vue和Angular双向数据绑定,计算属性等,数据是响应式的,控制室图刷新,拥有计算属性等,使得Vue和Angular需要状态管理的场景减少,此外其本身就包含了完整的状态管理工具,例如Vue的Vuex和Pinia,Angular的Service(RXjs)等,从官方定调;
-
React不一样,是一个纯UI的前端框架,UI = fn(state),React将状态的变动完全交给开发者。
三、React状态管理介绍
01. React状态管理工具的分类
-
React自带:Local Satae(props)和 context;
-
单向数据流:Flux、redux(Redu-toolkit);
-
双向数据绑定:Mobx;
-
原子型状态管理:recoil、Jotai;
-
异步操作密集型:Rxjs; 每一种状态管理工具都有其不同的适用性,不同场景下需要合理的选择状态管理工具;
-
子页面和子页面之间的通信,React本身提供了Context;
-
Local State:主要用于不同层级之间的通信,大多数子页面以及子页面下的组件之间的通信;
02. Context存在的问题
- Context相当于全局变量,难以追溯数据的变更情况;
- 使用context的组件内部耦合度太高,
- 会产生不必要的更新(比如会穿透memo和dependicies等);
- context只能存储单一值,无法存储多个各自拥有消费者的值的集合;
- 粒度不太好控制,不能细粒度的区分组件依赖了哪一个context;
- 多个context会存在层层嵌套的问题;
03. Redux
- Redux从Flux演变而来;
- 利用数据的单向流动的形式对公共状态进行管理;
04. Flux状态管理的框架
- Flux利用数据的单向流动对公共状态进行管理:
- Action:视图发出的信息;
- Dispatcher:派发着,用来接受Action,执行回调函数;
- Store:数据层,存放状态,一旦发生改动,就会更新数据以及emit相关事件等;
- Flux缺点:
- UI组件和容器组件的拆分过于复杂;
- Action和Dispatcher绑定在一起;
- 不支持多个store;
- store被频繁地引入和调用;
05. redux的三大原则
- 单一数据源:
- 整个应用的全局state都会爆窜爱在一个store中,一个单一数据源state tree 也简化了应用的调试和监控;
- 可以将应用数据持久化到本地,从而加速开发周期;
- 此外在单一数据源的原则之下,redux可以实现一些先前难以实现的功能,例如“撤销/重做”;
- store中的state是只读的:
- 不能修改store中的state,store中的state是只读;
- 唯一能改变store中的state的方式是通过“action”;
- 使用纯函数来执行修改:
- 接受纯函数来接受action,该纯函数称为reducer,可以改变store中的state;
06. React相关
- Redux可以进行时间旅行:
- 可以让应用程序切换到任意时间的状态;
- 如果复杂的场景,特别是存在页面组件间复杂的通信的场景非常适合使用Redux来管理;
- 比较适合应用于大型Web项目,尤其是一些交互复杂、组件通信频繁的场景;状态可预测和回溯是有价值的;
- 以及需要事故重现;
- React缺点:
- 为了实现纯函数的Reducer,Redux必须处理各种各样的副作用,需要引入一系列的副作用中间件,加重的心智负担;
- Action、Dispatch、Reducer的模式需要写过多的样板代码,虽然通过Reaciton Hooks和Redux tookit可以减少一定的样板代码,但是仍然有复杂度;
- 因此中小型项目,不太推荐使用React,可以选择使用Context或者react hooks 中的useReducer。
08. Mobx
- 概述:
- 可以通过透明的函数响应式编程使得状态管理变得简单和可扩展;
- Mobx和Vue的设计比较相似,是一个响应式的状态管理库;
- 借助于装饰器的实现,使得代码变得简洁;
- 由于使用了可观察对象,因此Mobx可以直接修改状态
- 使用步骤:
- 页面事件(生命周期、点击事件等)出发action的执行;
- 通过aciton来修改状态;
- 状态更新后,conputed计算属性也会根据依赖的状态来重新计算属性;
- 状态更新后会触发reaction,从而响应这次状态变化来进行一些操作(渲染组件、打印日志等)。
- 优势:
- 上手简单,可以直接修改状态,不需要编写繁琐的Action和Reducer;
- 也不需要引入各种复杂的中间件,局部精准更新,免去了力度控制烦恼,自始至终一份引用;
- 不需要immutabel,也没有复制对象的额外开销;
- 前端数据流不太复杂时会使用Mobx,更加清晰,且便于维护,较为灵活,但同时也导致Mobx的代码风格较难统一;
- 缺点:
- 代码风格难以统一;
- 不能实现时间旅行和回溯;
- 不太适合前端数据流比较复杂的场景;
- 随着React hooks,比如usereducer等以及React自身的原子型状态管理工具Recoil,Mobx的使用场景被逐渐压缩。
09. Recoil
- 概述:
- 是React官方内置的状态管理工具,一定程度上解决了Local State和Context的局限性,可以兼容React的新特性,例如Concurrent等;
- Recoil的核心就是Atom原子状态;
- 特点:
- 较为官方提供了与Concurrent模式以及其他React组件新特性兼容的可能性;
- 比较容易做到细粒度的状态控制;
- 也能实现状态回溯;
- 相较于Redux而言更加简单,不需要写很多的样板代码;
- 可以实现状态快照,比如填充首屏数据和数据状态回溯等;
- 具有原子型的原子状态,可以实现完美的局部更新;
- 解决的问题:
- 组件间的状态共享只能通过state提升至他们的公共祖先来实现,但是这样可能会导致重新渲染一棵巨大组件树;
- context只能存储单一值,无法存储多个格子拥有消费者的值的集合;
10. Zustand
- 主打轻量级;
- 没有臃肿的设计,也没有兼容React类组件的历史包袱;
- 使用简单;
- 体积很小,适合做移动端的网页;
- 使用简单,可以保存状态也可以在初始化的时候制定方法;
- 核心API和Redux相似,但是区别在于状体更新,Redux通过dispatch和reducer函数更新状态,Zustand通过setState来直接修改状态;
四、实现一个简易的状态管理工具
- store状态:
- 中介媒介;
- React UI层渲染;
五、Redux在项目中的实践
01. 如何使用Redux
Redux作为一款状态管理工具,主要是为了解决组件间通信的问题;
减少局部状态和redux状态的不合理混用;
02. Redux复杂的模版代码
- Redux遵循函数式编程的规则;
- action是一个原始js对象且reducer是一个纯函数;
- 对于同步切没有副作用的操作,上述数据流可以管理数据,从而控制视图层更新的目的;
03. Redux tookit
解决的问题;
- Redux需要太多的样板代码,中间件代码等,还需要区别同步和异步操作; 可以简化Redux开发,包含配置store、定义Reducer,不可变的更新逻辑,甚至可以立即创建整个状态“切片slice”。
六、补充
01. 全局状态管理库需要解决的问题
- 能够从组件树中的任何位置读取存储状态(最基本的功能);
- 能够写入存储状态;
- 提供优化渲染的机制;UI应当简单又高效;
- 提供优化内存占用的机制;
- 其他:
- 与并发模式的兼容;
- 数据序列化;
- 上下文丢失问题;
- 过期的属性问题;
- 僵尸子组件问题;
02. 其他
- 全局状态管理库最优是什么取决于开发者和使用场景的需求,难以定论;
- Redux属于【自上而下】的实现,但是随着时间推移,【自下而上】的实现方式逐渐兴起,倾向于状态都在树的顶部,下面的组件通过选择器获取它们需要的状态。