1 什么是状态管理
1.1 状态管理简介
解决“嵌套”低于的问题,解决的是跨层级组件之间的数据通信和状态共享。其实随着开发的深入,能不用的情况就不用
1.2 状态管理工具的本质
管理共享内存中的状态
- 共享内存
- 管理状态
- 页面通信
- 组建通信
- 刷新时效?
详细定义:单页应用的各个组件本身是共享内存的,如果将状态保存在内存中,就可以读写统一内存中的变量,从而达到状态共享的目的。
1.3 状态管理工具
- Vue:Vuex(Pinia)
- Angular:Service和Rxjs
- React:Flux、Redux、Mobx、Rxjs、Recoil、Jotal、Zustand React和Vue和Angular不一样,React是一个纯UI层的前端架构,UI=fn(state),React将状态的变动完全交给开发者。
2 React状态管理工具简介
2.1 分类
- React自带:Local State(props)和Context
- 单向数据流:Flux、Redux(Redux-toolkit)
- 双向数据绑定:Mobx
- 原子型状态管理:Recoil、Jotai
- 异步操作密集型:Rxjs 每一种状态管理工具都有其不同的适用性,不同场景下需要合理的选择状态管理工具。
Context
- 官方API
- 渲染粒度无法控制,经常引起不必要的渲染
- 耦合度高,层层嵌套
Flux
- 早期经典,已被redux平替
- dispatch和action耦合,store和action耦合
- 模板代码最大,不易理解
Redux
- 单向数据流
- 纯函数
- 时间旅行
- 模板代码复杂,副作用处理
- 支持类组件和函数组件
Redux-toolkit
- Redux的hook版
- 样板代码少
- 支持React新特性
Mobx
- 双向绑定,响应式
- api少,学习成本低,模板代码少
- 太灵活,很难保证代码风格统一
Recoll
- 原子性Atom,任意组合selector、细粒度更新(相比于context)
- 天然支持React Suspense
- 有快照功能
Jotai
- 简化版本的Recoll,2.4kb
- 细粒度更新
- 不需要Provider包裹,只有两个核心api:atom和useAtom
Zustand
- 轻量级,不到1kb,适合移动端
- 只支持hooksz7ujian
Rxjs
- 异步复杂逻辑处理
- 多值promise,丰富操作符,时间调度器
- 异步流程编排
xstate
- 基于状态机
valtio
- 基于proxy实现,类似mobx
- 变更历史和快照
2.1.1 Local State(props)
组件级别的局部状态
import {useState} from 'react'
const Hello = () => {
const [name,setName] = useState('Jony')
return <>Hello,{name}</>
}
上述的name就是一个最简单的局部Local State。它只在Hello这个组件中生效,当组件创建时初始化和生效,组件销毁时失效。
React的数据流是自上而下的,大部分情况下Local State可以满足需求。
例外
如果是同parent组件的同级组件之间想传递数据,优先考虑将状态向上一级,放在parent组件中,由parent组件自上而下的传递
当然如果一直这样延伸,会出现一个父组件签到十几层子组件的情况,这个“度”在前端大部分情况下就是子页面。
单页应用中,子页面以及子页面下的组件都是可以用Local State来解决状态管理问题的;而子页面之间,不再需要往上延伸——
子页面间的通信
子页面和子页面间的通信,React本身提供了Context。
2.2 Context
Context解决了React中,props或者state进行多级数据传递,则数据需要自顶下流经过每一级组件,无法跨级的问题。但是Context在页面间共享数据的时候同样有很多问题:
缺点
- Context相当于全局变量,难以追溯数据的变更情况
- 使用Context的组件内部耦合度太高,不利于组件的复用和单元测试
- 会产生不必要的更新(比如会穿透memo和dependicies等)
- Context只能存储单一值,无法存储多个各自拥有消费者的值的集合。
- 粒度也不太好控制,不能细粒度的区分组件依赖了哪一个Context
- 多个Context会存在层层嵌套的问题
解决方法
上述有部分缺点是可以解决的。
其实大部分场景下,不需要三方状态管理工具。Props和Context能解决很多问题。
经常放到Context中的内容:
- 一些全局的不需要经常变更的配置(主题、语言等)
- 在不同页面中但相同的属性
2.3 Redux(已淘汰)
Redux由Flux演变而来,利用数据的单向流动的形式对公共状态进行管理。由于已被淘汰,仅参考和借鉴其设计思想。Redux遵循函数式编程的规则。
Redux的前身Flux的状态管理
Flux利用数据的单向流动形式对公共状态进行管理
graph TD
a[Action] --> Dispatcher --> Store --> View
View --> b[Action] --> Dispatcher
Flux的架构图
- View:视图层
- Action:视图发出的消息
- Dispatcher:派发者,用来接收Action,执行回调函数
- Store:数据层,存放状态,一旦发生改动,就会更新数据以及emit相关事件等 例子
- 在UI页面中触发action
<button onClick = {this.handler.bind(this)}>click</button>
- 在FLux的Action中使用
dispatcher.dispatch将Action发送给Flux的dispatcher - dispatcher通过register注册事件,然后根据传递过来的action,来改变store中的state
- 在store中进行数据更新
- 在UI中监听store并触发更新 Flux的缺点
- UI组件和容器组件的拆分过于复杂
- Action和Dispatcher绑定在一起
- 不支持多个store
- store被频繁的引入和调用
Redux的构架图
Redux如何解决Flux的问题
- 解耦了action和dispatcher,可以直接通过dispatch发出一个action,不需要注册
- 解耦了store和dispatcher,提供了Reducer来处理Store的更新
Redux三大原则
- 单一数据源,只有一个store,全局state都保存在其中
- store中的state是只读的(唯一改变方法:通过action)
- 使用纯函数reducer来执行修改
Redux优缺点
优点
- 适用于大型项目
- 状态可预测和回溯
- 可以事故重现
缺点 - 相比Context或hooks的useReducer,不适用于中小型项目
- 需要引入一系列的副作用中间件
- 需要写过多的样板代码
2.4 Mobx
透明的函数响应式编程。设计类似Vue。使用了可观察对象,可以做到直接修改状态,不用编写actions和reducer
优点
- 上手简单
- 不用引入各种复杂的中间件
- 局部精确更新,不用担心粒度控制 缺点
- 太灵活,代码风格不统一,不方便维护
- 不能实现时间旅行和回溯
可优化替代的工具: - React hooks
- useReducer
- Recoil
2.5 Recoil
官方内置状态管理工具,一定程度上解决了Local State和Context的局限性,且能兼容新特性(Concurrent等)。
解决的问题
- 组件间的状态共享只能通过将state提升至他们的公共祖先来实现,但这样做可能导致重新渲染一棵巨大组件树
- Context只能存储单一值,无法存储多个各自拥有消费者的值的集合
核心
Atom原子状态,通过其可以派生出衍生状态Selector
特点
- 较为官方,提供React新特性的兼容的可能性
- 细粒度的状态控制
- 跟Redux实现状态回溯
- 理解起来没有很复杂,不用写很多样板代码
- 可以实现状态快照(填充首屏数据、数据状态回滚)
2.6 Zustand
主打轻量级,适合移动端网页开发。
初始化过程中,不仅能保存状态,也能在初始化的时候指定方法和函数。
Zustand核心API和Redux的区别
主要区别:状态的更新。
- Redux:dispatch和reducer
- Zustand:setState
3 实现一个简易的状态管理工具
发布/订阅模式
graph TD
Store状态 --发布--> 中介媒介 --发布--> a[React UI层渲染]--订阅--> 中介媒介 --订阅--> Store状态
案例:实现一个简单地Store
export default class CreateStore{
constructor(reducer,initialState){
this.currentReducer = reducer;
this.cuttentState = initialState;
this.listeners = [];
this.isDispatching = false;
}
getState(){
return this.currentState;
}
subscribe(listener){
this.listeners.push(listener);
return function unsubscribe(){
var index = this.listners.indexOf(listener);
this.listeners.splice(index,1);
};
}
dispatch(action){
try{
this.isDispatching = true;
this.currentState = currentReducer(currentState,action);
}finally{
this.isDispatching = false;
}
this.listeners.slice().forEach(listener => listener());
return action;
}
}
使用这个CreateStore来创建一个全局的状态
import CreateStore from './createStore.js'
function todos(state = [], action){
switch (action.type){
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}
const store = createStore(todos, ['Use Redux'])
store.dispatch({
type: 'ADD_TODO',
text: 'Read the docs'
})
console.log(store.getState())
4 Redux在项目中的实践
4.1 如何使用Redux
减少局部状态和redux状态的不合理混用
4.2 Redux复杂的模板代码
- 同步的、不存在副作用函数:
action:原始js对象;reducer:纯函数。
- 存在副作用函数:
在发出action,到reducer处理函数之间使用中间件处理副作用
中间件的作用
转换异步操作,生成原始action。这样reducer函数就能处理相应的action,从而改变state,更新UI。
4.3 Redux toolkit
解决Redux:样板代码、中间件代码太多,区别同步异步操作复杂的问题。简化开发。
5 个人总结和感想
个人只学过Vue没学过React,有很多知识点都没听说过,不过也有共通点。vue的状态管理基本就用vuex,没想到react会有那么多...