大纲:
- 状态管理定义
- React状态管理简介
- 实现一个简易的状态管理工具
- Redux在项目中的实践
状态管理定义 从React诞生之后,前端组件化的方案深入人心,React遵循的是单向数据流的原则,属性通过Props自上而下的传递。当页面的比较简单,组件之间的层级关系比较浅时,这种自上而下的单向数据流的方式是不会有问题的。如果页面一复杂,组件的嵌套层级一深,这种单向数据流的传递方式,将会使你陷入到“嵌套地狱”。
状态管理本身,解决的就是这种“嵌套”地狱的问题,解决的是跨层级组件之间的数据通信和状态共享。 状态管理工具的本质:管理共享内存中的状态
- 共享内存
- 管理状态
- 界面通信
- 组件通信
- 刷新失效 详细定义:单页应用的各个组件本身是共享内存的,如果将状态保存在内存中,就可以读写统一内存中的变量,从而达到状态共享的目的。 React状态管理工具 : Vue: Vuex(Pinia) Angular: Service和Rxjs React: Flux、Redux、Mobx、Rxjs、Recoil、Jotai、Zustand
跟不同前端框架的定义有关,Vue和Angular双向数据绑定,计算属性等,数据是响应式的,控制视图刷新,拥有计算属性等,这些使得Vue和Angular需要状态管理的场景减少,此外其本身就包含了完整的状态管理工具,比如Vue的Vuex和Pinia,Angular的Service(RXjs)等,从官方定调。而React不一样,React是一个纯UI层的前端框架,UI = fn(state),React将状态的变动完全交给开发者。
状态管理工具简介
React状态管理工具可以分为以下几类:
○ React自带: Local State(props) 和Context ○ 单向数据流:Flux、Redux(Redux-toolkit) ○ 双向数据绑定:Mobx ○ 原子型状态管理:Recoil、Jotai ○ 异步操作密集型:Rxjs
每一种状态管理工具都有其不同的适用性,不同场景下需要合理的选择状态管理工具。
- Lacal State(prop local State顾名思义,就是组件级别的局部状态,比如: 上述的name就是一个最简单的局部local State。只在Hello这个组件中生效,当组件创建时初始化和生效,组件销毁时失效
React的数据流是自上而下的,大部分情况下local State就能满足我们的需求
当然这种向上延伸的方法,不是无限的,如果一直往上延伸,会出现一个父组件嵌套10几层子组件的情况,必须要有一个“度”,超过这个“度”后,我们就认为local State的方式就不太实用了。
这个“度”,在前端开发中,大部分情况下我们认为就是子页面。我们一般认为,单页应用中,子页面以及子页面之下的组件都是可以用local State来解决状态管理问题的,而子页面和子页面之间,是不需要再往上延伸。那么子页面和子页面之间的通信,React本身提供了Context。
- Context React中的Context解决了react中,props或者state进行多级数据传递,则数据需要自顶下流经过每一级组件,无法跨级的问题。但是Context在页面间共享数据的时候同样有很多问题:
a. Context相当于全局变量, 难以追溯数据的变更情况 b. 使用Context的组件内部耦合度太高,不利于组件的复用和单元测试 c. 会产生不必要的更新(比如会穿透memo和dependicies等) d. Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合。 e. 粒度也不太好控制,不能细粒度的区分组件依赖了哪一个Context f. 多个Context会存在层层嵌套的问题
上述的缺点,有部分其实都是能够解决的。在React业务代码的开发中,要多思考,其实大部分场景下,我们都不需要三方状态管理工具。PropsContext能解决我们很多问题。
Context的使用场景很多,一些全局的不需要经常变更的配置,我们经常放到Context中,比如主题,语言等: 此外,比如不同界面中有一些相同的属性,哦我们也可以放在Context中
- Flux
Flux它是 Facebook 官方给出的应用架构,利用数据的单向流动的形式对公共状态进行管理,不过现在已经被淘汰了,不过其设计思想还是可以参考和借鉴的。 View:视图层 Action:视图发出的消息 Dispatcher:派发者,用来接收Action,执行回调函数 Store:数据层,存放状态,一旦发生改动,就会更新数据以及emit相关事件等
Flux构架的过程: i. 在UI界面发出action ii. 在Flux的Action中使用dispatcher.dispatch将Action发送给Flux的dispatcher iii. dispatcher通过register注册事件,然后根据传递过来的action,来改变store中的state iv. 在store中进行数据更新、 v. 在UI中监听store并触发更新
Flux的缺点: § UI组件和容器组件的拆分过于复杂 § Action和Dispatcher绑定在一起 § 不支持多个store § store被频繁的引入和调用
-
Redux Redux是从Flux演变而来的。 Redux中的store同样也解除了dispatcher的耦合,提供了一个Reducer来处理Store的更新
Redux的三大原则:单一数据源,只有一个store、store中的state是只读的、使用纯函数来执行修改。
1.单一数据源: 在redux中,整个应用的全局State(再次注意是全局state),都会保存在一个store中,一个单一数据源 state tree 也简化了应用的调试和和监控;它也让你在开发中能将应用数据持久化到本地,从而加速开发周期。此外,有一些功能以前很难实现,比如“撤销/重做”,在单一数据源的原则下,使用 Redux 实现将非常容易。
2.Store中的State是只读的: 我们不能直接修改store中的state,store中的state是只读的。唯一能改变store中的state的方式就是通过action
3.使用纯函数来执行修改: 接受纯函数来接受aciton,该纯函数叫reducer,可以改变store中的state. 因为Redux的上述特性,使得Redux可以做时间旅行。时间旅行:顾名思义,就是可以随时穿越到以前和未来,让应用程序切换到任意时间的状态。因此,如果复杂的场景,特别是存在页面组件间复杂的通信的场景非常适合用Redux来管理状态。
Redux 比较适合用于大型 Web 项目,尤其是一些交互足够复杂、组件通信频繁的场景,状态可预测和回溯是非常有价值的。还有一种场景,比如需要事故重现,这种定义和上报事故异常和重现的场景,Redux也很有意义。
Redux的缺点也很明显,首先为了实现纯函数的Reducer,Redux必须处理各种各样的副作用,需要引入一系列的副作用中间件,加重的心智负担,此外Action,Dispatch,Reducer的模式需要写过多的样版代码,虽然通过React hooks和Redux toolkit可以减少一定的样板代码,但是复杂度还是摆在哪里。因此中小项目,也不太推荐使用Redux,可能Context或者React hooks中的useReducer就能满足你的需求。