React状态管理|青训营笔记

108 阅读8分钟

第十一节 React状态管理

一、前言

  1. 为什么不直接使用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存在的问题

  1. Context相当于全局变量,难以追溯数据的变更情况;
  2. 使用context的组件内部耦合度太高,
  3. 会产生不必要的更新(比如会穿透memo和dependicies等);
  4. context只能存储单一值,无法存储多个各自拥有消费者的值的集合;
  5. 粒度不太好控制,不能细粒度的区分组件依赖了哪一个context;
  6. 多个context会存在层层嵌套的问题;

03. Redux

  • Redux从Flux演变而来;
  • 利用数据的单向流动的形式对公共状态进行管理;

04. Flux状态管理的框架

  1. Flux利用数据的单向流动对公共状态进行管理:
  • Action:视图发出的信息;
  • Dispatcher:派发着,用来接受Action,执行回调函数;
  • Store:数据层,存放状态,一旦发生改动,就会更新数据以及emit相关事件等;
  1. Flux缺点:
  • UI组件和容器组件的拆分过于复杂;
  • Action和Dispatcher绑定在一起;
  • 不支持多个store;
  • store被频繁地引入和调用;

05. redux的三大原则

  1. 单一数据源:
  • 整个应用的全局state都会爆窜爱在一个store中,一个单一数据源state tree 也简化了应用的调试和监控;
  • 可以将应用数据持久化到本地,从而加速开发周期;
  • 此外在单一数据源的原则之下,redux可以实现一些先前难以实现的功能,例如“撤销/重做”;
  1. store中的state是只读的:
  • 不能修改store中的state,store中的state是只读;
  • 唯一能改变store中的state的方式是通过“action”;
  1. 使用纯函数来执行修改:
  • 接受纯函数来接受action,该纯函数称为reducer,可以改变store中的state;

06. React相关

  1. Redux可以进行时间旅行:
  • 可以让应用程序切换到任意时间的状态;
  • 如果复杂的场景,特别是存在页面组件间复杂的通信的场景非常适合使用Redux来管理;
    • 比较适合应用于大型Web项目,尤其是一些交互复杂、组件通信频繁的场景;状态可预测和回溯是有价值的;
    • 以及需要事故重现;
  1. React缺点:
  • 为了实现纯函数的Reducer,Redux必须处理各种各样的副作用,需要引入一系列的副作用中间件,加重的心智负担;
  • Action、Dispatch、Reducer的模式需要写过多的样板代码,虽然通过Reaciton Hooks和Redux tookit可以减少一定的样板代码,但是仍然有复杂度;
  • 因此中小型项目,不太推荐使用React,可以选择使用Context或者react hooks 中的useReducer。

08. Mobx

  1. 概述:
  • 可以通过透明的函数响应式编程使得状态管理变得简单和可扩展;
  • Mobx和Vue的设计比较相似,是一个响应式的状态管理库;
  • 借助于装饰器的实现,使得代码变得简洁;
  • 由于使用了可观察对象,因此Mobx可以直接修改状态
  1. 使用步骤:
  • 页面事件(生命周期、点击事件等)出发action的执行;
  • 通过aciton来修改状态;
  • 状态更新后,conputed计算属性也会根据依赖的状态来重新计算属性;
  • 状态更新后会触发reaction,从而响应这次状态变化来进行一些操作(渲染组件、打印日志等)。
  1. 优势:
  • 上手简单,可以直接修改状态,不需要编写繁琐的Action和Reducer;
  • 也不需要引入各种复杂的中间件,局部精准更新,免去了力度控制烦恼,自始至终一份引用;
  • 不需要immutabel,也没有复制对象的额外开销;
  • 前端数据流不太复杂时会使用Mobx,更加清晰,且便于维护,较为灵活,但同时也导致Mobx的代码风格较难统一;
  1. 缺点:
  • 代码风格难以统一;
  • 不能实现时间旅行和回溯;
  • 不太适合前端数据流比较复杂的场景;
  • 随着React hooks,比如usereducer等以及React自身的原子型状态管理工具Recoil,Mobx的使用场景被逐渐压缩。

09. Recoil

  1. 概述:
  • 是React官方内置的状态管理工具,一定程度上解决了Local State和Context的局限性,可以兼容React的新特性,例如Concurrent等;
  • Recoil的核心就是Atom原子状态;
  1. 特点:
  • 较为官方提供了与Concurrent模式以及其他React组件新特性兼容的可能性;
  • 比较容易做到细粒度的状态控制;
  • 也能实现状态回溯;
  • 相较于Redux而言更加简单,不需要写很多的样板代码;
  • 可以实现状态快照,比如填充首屏数据和数据状态回溯等;
  • 具有原子型的原子状态,可以实现完美的局部更新;
  1. 解决的问题:
  • 组件间的状态共享只能通过state提升至他们的公共祖先来实现,但是这样可能会导致重新渲染一棵巨大组件树;
  • context只能存储单一值,无法存储多个格子拥有消费者的值的集合;

10. Zustand

  • 主打轻量级;
  • 没有臃肿的设计,也没有兼容React类组件的历史包袱;
  • 使用简单;
  • 体积很小,适合做移动端的网页;
  • 使用简单,可以保存状态也可以在初始化的时候制定方法;
  • 核心API和Redux相似,但是区别在于状体更新,Redux通过dispatch和reducer函数更新状态,Zustand通过setState来直接修改状态;

四、实现一个简易的状态管理工具

  • store状态:
  • 中介媒介;
  • React UI层渲染;

五、Redux在项目中的实践

01. 如何使用Redux

Redux作为一款状态管理工具,主要是为了解决组件间通信的问题;

image.png 减少局部状态和redux状态的不合理混用;

02. Redux复杂的模版代码

  • Redux遵循函数式编程的规则;
  • action是一个原始js对象且reducer是一个纯函数;
  • 对于同步切没有副作用的操作,上述数据流可以管理数据,从而控制视图层更新的目的;

03. Redux tookit

解决的问题;

  • Redux需要太多的样板代码,中间件代码等,还需要区别同步和异步操作; 可以简化Redux开发,包含配置store、定义Reducer,不可变的更新逻辑,甚至可以立即创建整个状态“切片slice”。

六、补充

01. 全局状态管理库需要解决的问题

  • 能够从组件树中的任何位置读取存储状态(最基本的功能);
  • 能够写入存储状态;
  • 提供优化渲染的机制;UI应当简单又高效;
  • 提供优化内存占用的机制;
  • 其他:
    • 与并发模式的兼容;
    • 数据序列化;
    • 上下文丢失问题;
    • 过期的属性问题;
    • 僵尸子组件问题;

02. 其他

  • 全局状态管理库最优是什么取决于开发者和使用场景的需求,难以定论;
  • Redux属于【自上而下】的实现,但是随着时间推移,【自下而上】的实现方式逐渐兴起,倾向于状态都在树的顶部,下面的组件通过选择器获取它们需要的状态。