大纲:
1. 状态管理定义
2. React状态管理简介
3. 实现一个简易的状态管理工具
5. Mobx
它通过透明的函数响应式编程使得状态管理变得简单和可扩展, Mobx跟Vue的设计比较相似,是一个响应式的状态管理库。Mobx 借助于装饰器的实现,使得代码更加简洁易懂。由于使用了可观察对象,所以 Mobx 可以做到直接修改状态,而不必像 Redux 一样编写繁琐的 actions 和 reducers。
Mobx 的优势在于上手简单,可以直接修改状态,不需要编写繁琐的 Action 和 Reducer,也不需要引入各种复杂的中间件,局部精确更新,免去了粒度控制烦恼,自始至终一份引用,不需要 immutable,也没有复制对象的额外开销。因此前端数据流不太复杂的情况,使用 Mobx,因为更加清晰,也便于维护。但是正是因为Mobx的灵活,Mobx的代码风格很难统一。
不过Mobx是不能实现时间旅行和回溯的,因此不太适合前端数据流比较复杂的场景,此外,随着React hooks,比如useReducer等的,以及React自身的原子型状态管理工具Recoil。Mobx的使用场景会被进一步压缩,目前的项目中使用Mobx的场景已经越来越小。
- Recoil
Recoil是React官方内置的状态管理工具,一定程度上解决了Local State和Context的局限性,且能够兼容React的新特性比如Concurrent 模式等。
解决的问题: a. 组件间的状态共享只能通过将 state 提升至它们的公共祖先来实现,但这样做可能导致重新渲染一颗巨大组件树。 b. Context 只能存储单一值,无法存储多个各自拥有消费者的值的集合。
○ Recoil侧是更加的具有原子性,比如在Recoil的状态都是Atom,可以进行任意组合等。 ○ Recoil具有原子性的原子状态,可以实现完美的局部更新
Recoil的核心,就是Atom原子状态,一集通过Atom原子状态可以派生出衍生状态Selector。
Recoil主要特点,就是较为官方,提供了与 Concurrent 模式及其他 React 新特性兼容的可能性,主打的是性能。此外因为其原子性的特点,比较容易做到细粒度的状态控制。也能跟Redux实现状态回溯,相比较Redux而言,还有一个特点就是理解起来没有很复杂,不需要写很多样板代码等。
Recoil还有一个特点就是可以实现状态快照。比如填充首屏数据和数据状态回滚等。
- Zustand
Zustand是主打轻量级的状态管理工具,没有Redux那样臃肿的设计,也没有兼容React类组件的历史包袱,Zustand状态管理工具体积很小,因此很适合移动端的网页。
Zusand的使用极其简单,初始化过程中,我们不仅能保存状态,也能在初始化的时候制定方法和函数。
Zustand库的核心API和Redux极为相似,区别主要在状态的更新,Redux通过dispatch和reducer函数来进行状态更新,而Zustand则是可以通过setState来直接修改状态。
Redux Zustand
创建Store createStore(reducer, [preloadedState], [enhancer]); createStore();
获取state getState(); getState();
监听state的变化 subscribe(listener); subscribe(listener);
更新state dispatch(action); 执行reducer setState();
zustand 通过 Object.assign 函数合并更新状态,同时提供 replace 标志位直接将旧状态完全替换。而 redux 的状态更新则要复杂一些,主要是官方推荐的编程模式将状态更新拆分为多个步骤,dispatch() 函数触发一个 Action,而具体处理 Action 以及状态合并的操作均由Reducer 函数完成,该函数是一个纯函数。这么设计的原因是纯函数对于状态变化来说是可预测的,而且利于测试,更是实现时间旅行类似功能的基础。
Redux在项目中的实践
- 正确使用Redux 首先来看redux的本质:
redux做为一款状态管理工具,主要是为了解决组件间通信的问题。 既然是组件间的通信问题,那么显然将所有页面的状态都放入redux中,是不合理的,复杂度也很高。
减少局部状态和redux状态的不合理混用:
全量使用redux的复杂度很高,我们当然考虑将一部分状态放在redux中,一部分状态放在local state中,但是这种情况下,很容易产生一个问题,就是如果local State跟redux中的state存在状态依赖。
- Redux的复杂模板代码
redux是遵循函数式编程的规则,上述的数据流中,action是一个原始js对象(plain object)且reducer是一个纯函数,对于同步且没有副作用的操作,上述的数据流起到可以管理数据,从而控制视图层更新的目的。
如果存在副作用函数,那么我们需要首先处理副作用函数,然后生成原始的js对象。如何处理副作用操作,在redux中选择在发出action,到reducer处理函数之间使用中间件处理副作用。
在有副作用的action和原始的action之间增加中间件处理,从图中我们也可以看出,中间件的作用就是:转换异步操作,生成原始的action,这样,reducer函数就能处理相应的action,从而改变state,更新UI
因为中间件,纯函数Reducer等使得Redux需要写很多样板代码,使用起来越来越复杂,早期我们使用redux-thunk,或者redux-saga等,但是复杂度还是在那里,因此在项目中不推荐使用如此复杂的Redux以及相关逻辑。
- Redux toolkit
用redux需要有太多的样板代码,中间件代码等等,还需要区别同步和异步操作,及其复杂。早期Dvajs通过封装,能够解决部分上述的问题。早期的Redux,我们也需要引入很多Redux相关的包,比如React-redux等等,显得复杂而繁琐,而Redux toolkit的出现则是完全解决了上述的问题,使得Redux的开发可以简单明了。Redux toolkit是官方推荐的高效的redux状态管理工具集。
Redux toolkit可以简化Redux开发,包括配置 store、定义 reducer,不可变的更新逻辑、甚至可以立即创建整个状态的 “切片 slice”,而无需手动编写任何 action creator 或者 action type。此外,Redux toolkit提供了完整的React的hooks,可以方便React函数组件中使用Redux toolkit。
整体来看,当context中的数据一复杂之后,context的使用也会变得比较复杂,代码的可读性一定程度下会下降,此外最主要的是我们不太好追踪数据的变化。