什么是状态管理
从Reacti诞生之后,前端组件化的方案深入人心,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是一个纯Ul层的前端框架,UI=fn(state),React将状态的变动完全交给开发者。
状态管理工具简介
React状态管理工具可以分为以下几类:
-
Redux:Redux是一个可预测的状态容器,具有单一数据源、纯函数操作和时间旅行等特性。它通过将应用程序的状态保存在一个全局存储区域中统一管理应用程序的状态,并且允许你在任何地方使用并更新这个状态。
-
MobX:Mobx的优势在于上手简单,可以直接修改状态,不需要编写繁琐的Action和Reducer,也不需要引入各种 复杂的中间件,局部精确更新,免去了粒度控制烦恼,自始至终一份引用,不需要immutable,也没有复制对象 的额外开销。因此前端数据流不太复杂的情况,使用Mobx,因为更加清晰,也便于维护。但是正是因为Mobx 的灵活,Mobx的代码风格很难统一。 不过Mobx是不能实现时间旅行和回溯的,因此不太适合前端数据流比较复杂的场景,此外,随着React hooks,比如useReducer等的,以及React自身的原子型状态管理工具Recoil。Mobx的使用场景会被进一步压缩, 目前的项目中使用Mobx的场景已经越来越小。
-
Context API:React官方提供了Context API,允许您在组件树中共享状态而无需通过层层传递props。它通常适用于应用程序中只有很少需要共享的状态或者状态较为简单的情况。
-
Recoil:Recoil是Facebook开发的一个新型状态管理工具,旨在提供轻量级的、高度可组合的状态管理机制。Recoil基于原子状态的概念,其中原子状态是粗粒度、可复用的状态单元。
-
Flux:Flux是一个使用单项数据流架构来管理状态的应用程序框架,它提供了一个指导性的架构,使得应用程序中的控制逻辑更为明确和易于维护。
-
Zustand:Zustand 是一个小巧的状态管理库,它使用 React Hooks 和 Context 来管理状态。Zustand 的设计哲学是让状态管理变得简单和直接,避免使用 Redux 的样板代码和复杂性。Zustand 还提供了一些特性,例如中间件和状态切片,使得状态管理更加灵活和可控。
-
Effector:Effector是一个响应式状态管理库,旨在提供更高效、更灵活的状态管理机制,同时还提供了丰富的开发工具和钩子函数,以帮助提高开发人员的工作效率。
-
Apollo Client:Apollo Client是一个用于与GraphQL服务器交互的JavaScript库,它提供了复杂的状态管理和查询缓存功能,使得数据获取和状态管理变得更加容易。
每一种状态管理工具都有其不同的适用性,不同场景下需要合理的选择状态管理工具。
实现一个简易的状态管理工具
class Store {
constructor() {
this.state = {};
this.listeners = {};
}
getState() {
return this.state;
}
setState(newState) {
this.state = {...this.state, ...newState};
this.notifyListeners();
}
notifyListeners() {
Object.values(this.listeners).forEach(listener => listener());
}
subscribe(key, listener) {
this.listeners[key] = listener;
return () => {
delete this.listeners[key];
}
}
}
该Store包括了getState、setState、notifyListeners和subscribe四个核心方法。
- getState函数返回当前状态。
- setState函数用于修改状态,并且在修改后触发监听器的回调函数。
- notifyListeners函数遍历所有已注册的监听器并调用其回调函数。
- subscribe函数用于注册一个监听器,并返回一个函数用于取消该监听器。
使用示例:
// 创建store
const store = new Store();
// 订阅state变化
const unsubscribe = store.subscribe('example', () => {
console.log(store.getState());
});
// 修改state
store.setState({ count: 1 }); // 输出{count: 1}
store.setState({ count: 2 }); // 输出{count: 2}
// 取消订阅
unsubscribe();
该示例中创建了一个简单的Store,并注册了一个监听器,在修改state后输出相应的新值。最后取消订阅。
Redux在项目中的实践
如何使用Redux
redux做为一款状态管理工具,主要是为了解决组件间通信的问题。 既然是组件间的通信问题,那么显然将所有页面的状态都放入redux中,是不合理的,复杂度也很高。
减少局部状态和redux状态的不合理混用:
全量使用redux的复杂度很高,我们当然考虑将一部分状态放在redux中,一部分状态放在localstate中,但是这种情况下,很容易产生一个问题,就是如果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以及相关逻辑。
总结
React状态管理是开发React应用不可或缺的一部分,它可以帮助我们高效地管理组件之间复杂的数据流动和状态变化。现阶段常见的React状态管理方案有:Redux、MobX、Context API、以及基于Hooks的状态管理库等。
对于选择合适的React状态管理方案,需要根据具体业务需求、项目规模、开发团队的技术水平等因素进行考虑。在此过程中,我认为以下几点需要特别注意:
-
首先,需要对自身项目的状态管理需求有一个充分的认识。如何定义、设计、存储、更新和传递状态,这些都需要在实践中考虑清楚,并沉淀成更加通用、共享的状态管理方案。
-
其次,在对各种状态管理方案进行权衡时,需要结合具体的上手难度、学习曲线、代码复杂度等因素进行评估。例如,在小型单页面应用中使用React自带的useState、useReducer等API即可满足需求,而对于大型多页面应用则需要Redux作为整个应用状态的统一管理器来处理各种数据流操作。
-
最后,在工作中进行React状态管理的过程中,需要不断总结、思考,寻找更好的解决方案,探索是否可以通过某些方式进一步简化状态管理的过程、提升代码质量等。同时,也可以参考社区中优秀的开源项目和社区分享来加深我们的理解和应用。总之,在探索 React超强生态系统时,不断学习、实践并总结优秀的实践经验是非常重要的。