🗒️状态管理库选型
type
status
date
slug
summary
tags
category
icon
password
Blocking
Blocked by
top
URL
Sub-item
Parent item
本质
状态管理库简单理解为全局变量(虽然不是很恰当),可以在任意地方进行状态的读取和修改。
而一个完善的状态管理库需要考虑的点:
- render颗粒度。
- 异步修改。
- 派生状态。
- 是否支持非react组件内的状态操作。
- 是否支持中间件扩展。
原生问题
系统自带的Provider + Context(useContext)
一个完整的例子
用法
context 可以实现基础的状态共享。步骤:
- 通过createContext创建一个Context,并设置默认值。
- Context 中的值都在 Provider 的作用域下有效。所以在最外层包一个 Provider,value中设置传递值。
- 如果子组件需要时,则通过 React.useContext拿到Context值。
缺点
- 重复渲染,性能差
当 Context 中的某个属性发生变化时,所有使用该 Context 的子组件都会被重新渲染,即使某些组件可能并未使用到这个属性。
这种问题有两种解决方法:
- React.memo 和 shouldComponentUpdate 这两种方式来解决。
- 将一个大型的 Context 拆分成多个小的 Context,可以精细地控制每个 Context 的更新,从而减少整个应用程序的重新渲染次数,提高性能和用户体验。
- 数据变更方法难以维护
为了保证数据变更方法的可维护性与 action 的不变性,有两种方法但各有问题:使用自定义 hooks,但为了不会重复渲染,每个修改方法需要使用useCallback,需要声明依赖,会造成极大的心智负担。使用useReducer,不支持异步函数、不支持内部的 reducer 互相调用,不支持和其他 state 联动(比如要当参数传进去才可用)。
- 使用范围限制
context的store只能在react中使用,无法在外部函数使用,例如请求函数中需要store中的属性,不能直接在请求函数中调用,就只能通过react中作为参数传递。
- 无法处理异步请求。
对于异步的逻辑,Context API并没有提供任何API,需要自己做封装。
- 无法处理数据间的联动
Context API并没有提供API来生成派生状态,同样也需要自行去封装一些方法来实现。
各状态库评测
流派总结
- 没有状态管理工具:直接用 props 或者 context
- 单项数据流:
redux
、zustand
。
状态是由一个单一的“源”(通常称为“store”)管理的,并通过将状态从store传递到子组件,然后再将子组件的“actions”传递回store来更新状态。
- 双向绑定:
mobx
、valtio
。
内部对通过 state 绑定的组件,添加到了订阅者队列,store中的属性相当于一个被观察者,当属性状态变更后,通知所有订阅了该数属性的组件进行更新。
- 状态原子化:
jotai
、recoil
状态被分解成多个不可分割的小状态,并且通过原子操作的方式来更新这些小状态。
状态库对比
框架 | 流派 | 学习成本 | 派生状态(带缓存) | 组件外调用 | 性能 | 异步支持 |
hooks context | Context | 低 | 不支持 | 支持 | 差 | 友好 |
react-redux | 单项数据流 | 高 | 不支持,需要中间件 | 支持 | 中等 | 复杂 |
zustand | 单项数据流 | 低 | 不支持,需要中间件 | 支持 | 中等 | 友好 |
mobx | 双向绑定 | 高 | 支持 | 支持 | 好 | 友好 |
valtio | 双向绑定 | 中 | 不支持,需要中间件 | 支持 | 好 | 友好 |
jotai | 状态原子化 | 低 | 支持 | 不支持 | 中等 | 友好 |
recoil | 状态原子化 | 中 | 支持 | 不支持 | 中等 | 友好 |
总结
- 简单项目使用useState + Context,会存在重复渲染的性能问题。
- 复杂项目使用zustand,支持react组件外引用,但性能优化需要手动处理。
- 高性能需求项目使用Valtio,基于 proxy 的方案,提供全自动的渲染优化。