状态管理是前端整天遇到的概念,但是大家是否思考过什么是状态,管理的又是什么呢? 我们知道,程序是处理数据的,数据是信息的载体,比如颜色是红色或蓝色这就是数据。 那为什么不叫数据管理呢?状态和数据是什么关系? 什么是状态 状态是数据的变化,比如颜色是红色或蓝色是数据,而颜色从红色变为蓝色这就是状态了。 状态的改变对应着视图的渲染或者某段逻辑的执行。比如颜色从红色变为蓝色可能就要重新渲染视图,并且执行发送请求到服务端的逻辑。 通过视图交互或者其他方式触发状态的变化,状态变化联动视图的渲染和逻辑的执行,这就是前端应用的核心。 为什么之前jQuery时代没咋听说状态管理的概念,而Vue、React时代经常听到呢? jQuery时代是手动把数据渲染到视图和执行数据变化之后的逻辑的,它可能没有明确的状态这一层,而是直接把数据渲染成dom,下次需要数据也是从dom来取的。 而Vue、React前端框架的时代不需要手动操作dom和执行数据变化之后的逻辑,只要管理好状态,由前端框架负责状态变化之后的处理。 状态管理管理的是什么呢? 什么是状态管理 状态管理具体有两层含义: 状态变化之前的逻辑,一般是异步的。 状态变化之后的联动处理,比如渲染视图或执行某段逻辑。 比如React的setState不会马上修改状态,而是异步的批量的执行,把状态做一下合并。 比如Redux的action在修改全局state之前也是要经历中间件的处理的。 这些都是状态变化之前的异步过程的管理,是状态管理的第一层含义。 再比如ReactsetState修改了状态之后要触发视图的渲染和生命周期函数的执行,hooks在依赖数组的状态变化之后也会重新执行。(vue的data修改之后会重新渲染视图、执行computed和watch逻辑) Redux修改了全局状态之后要通知组件做渲染或者做其他逻辑的处理,Vuex、Mobx等都是。 这些是状态变化之后的联动处理的管理,是状态管理的第二层含义。 我们知道了什么是状态,什么是状态管理,那前端框架Vue、React和全局状态管理的库Redux、Mobx、Vuex都是怎么实现状态管理的呢? 状态管理的两种实现思路 状态不会是一个,多个状态的集合会用对象的key、value来表示,比如React的state对象,Vue的data对象(虽然叫data也是指的状态)。 怎么监听一个对象的变化呢? 我们是不是可以提供一个api来修改,在这个api内做state变化之前的处理,并且在state变化之后做联动处理。 这样的方案只能通过api触发状态修改,直接修改state是触发不了状态管理逻辑的。 React的setState就是这种思路,通过setState修改状态会做状态变化之前的批量异步的状态合并,会触发状态变化之后视图渲染和hooks、生命周期的重新执行。但是直接修改state是没用的。 那怎么让直接修改状态也能监听到变化呢? 可以对状态对象做一层代理,代理它的get、set,当执行状态的get的时候把依赖该状态的逻辑收集起来,当set修改状态的时候通知所有依赖它的逻辑(视图渲染、逻辑执行)做更新。 Vue的data监听变化就是用的这种思路,在状态get的时候把依赖封装成Watcher,当set的时候通知所有Watcher做更新。 这种思路叫做响应式(reactive),也就是状态变化之后自动响应变化做联动处理的意思。 代理get、set可以用Object。defineProperty的api,但是它不能监听动态增删的对象属性,所以Vue3改为了用Proxy的api实现。 监听对象的变化就这两种方式: 提供api来修改,内部做联动处理。 对对象做一层代理,set的时候做联动处理,通知get时收集的所有依赖。 前端框架状态变化的性能优化 但是频繁的修改state不是每次都要做联动处理,有一些可以合并的,比如两次都把颜色改为红色,那后续逻辑就没必要执行两次,需要再做些性能方面的优化。 所以React的setState是异步的,会做批量的state合并(注意,React的setState传入的不是最终的state,而是state的diff,React内部去把这些diffstate更新到state)。 图片 而Vue因为是直接修改的同一个对象,所以没必要做啥合并,它的Watcher执行是异步的,对多次放到队列里的Watcher做下去重就行了。 图片 组件间的状态管理 组件内的状态管理就是这样的,利用前端框架自带的state机制来管理。 那组件之间呢?一个组件的state变了如何联动其他组件变化? props 通过props,把当前组件的state作为props传入其他组件就行了,这样就能联动变化。 但是props只能一层层传递,如果组件和想联动变化的组件相隔很多层,传递props就很麻烦。 这种情况下前端框架也都提供了解决方案,React提供了Context、Vue提供了EventBus。 Context、EventBus React组件可以在context中存放state,当context中的state变化的时候会直接触发关联组件的渲染。 Vue可以在一个组件内emit一个事件,然后另一个组件on这个事件,然后更新自己的data来触发渲染。不过这两个api在Vue3都废弃了。 这种前端框架自带的任意层组件的状态联动方案只能处理简单的场景,复杂的场景还是得用全局状态管理库,比如Redux、Vuex、Mobx这些。 为什么这么说呢? 还记得状态管理的两层含义么?状态变化前的异步过程的管理,状态变化后的联动处理。 Context和EventBus都只做到了状态变化后的联动处理,但是没有对状态变化前的异步过程管理做支持。 比如多个组件都要修改context中的值(或者通过eventbus修改全局状态),这个过程都要执行一段异步逻辑,要做loading的展示,那多个组件里怎么复用这段loading的逻辑呢? 还有,如果异步过程比较麻烦,需要用rxjs这样的库,用context和eventbus的方案怎么和rxjs结合呢? 当然,是可以对context和eventbus做一些逻辑复用的封装和一些结合rxjs方案之类的封装的,但是比较麻烦。 而且更重要的是如果你想做这些的时候,那也就没必要用context和eventbus了,直接用全局状态管理库就行。 Redux、Mobx、Vuex redux就提供了中间件的机制,组件里发送action到store(存放全局state的地方),之前会经历层层中间件的处理,在这里就可以做一些可复用的逻辑的封装,比如loading的处理,也可以结合rxjs这种异步过程处理方案。 redux里最常用的中间件就是reduxsaga和reduxobservable了,这俩都是做异步过程的管理的。 reduxsaga是基于generator实现的,不管是同步还是异步,都只要声明式的描述要执行的逻辑就行,由saga内部的执行器会去做同步或异步的处理,描述异步逻辑就很简洁,而且reduxsaga提供了很多内置的逻辑封装。 图片 reduxobservable则是结合rxjs的方案了,把action变成数据源,经历层层opreator的处理,最后传递到store。可以用rxjs生态大量的oprator,做下组装就行,根本不用自己写异步逻辑的具体实现。 图片 mobx没有提供中间件机制,它的action是执行状态class的某个方法,可以用class的那套来做封装。 有的同学对这些状态管理库不太熟,简单来介绍下。 我们理清了状态管理的实现只有两种方案,一种是提供api做修改,一种是对state对象做响应式代理。 前端框架的状态管理是这样,独立的全局状态管理库也同样是这样。 redux就是提供api来修改的方案,通过reducer函数来对传入的action做处理,返回新的state。 图片 而且redux这种思路是函数式的思想,每个reducer都是输入和输出一一对应的纯函数,返回的state都是全新的,为了方便创建新的state,一般会搭配immutable库,只要修改属性就会返回新的state对象。 mobx是响应式代理的方案,它对全局state做了一层代理(通过Object。defineProperty),状态的get收集依赖,set的时候触发依赖更新。 图片 所以这种方案很自然的可以把全局state组织成一个个class,是面向对象的思想,可以通过继承等方式实现逻辑复用。 importReact,{Component} importReactDOM import{observer} observer classTodoListViewextendsComponent{ render(){ return {this。props。todoList。todos。map(todo )} ul Tasksleft:{this。props。todoList。unfinishedTodoCount} } } constTodoViewobserver(({todo}) input typecheckbox checked{todo。finished} onClick{()todo。finished!todo。finished} {todo。title} li ) conststorenewTodoList(); ReactDOM。render(,document。getElementById(mount)); vuex则像是两种思路的结合,内部是用响应式代理来实现的变化监听,但是暴露出的api却是redux的action那一套。 图片 和React搭配使用的话,需要把组件添加到状态的依赖中,这个不用自己调用subscribe之类的api,直接用一些封装好的高阶组件(接受组件作为参数返回新的组件的组件)就行,比如reactredux的connect,mobxreact的observer。 总结 讲了这么多,回过头来看一下就会发现: 不管是前端框架内置的组件内状态变化管理的方案(react的setState、vue的直接修改data),还是前端框架提供的组件间的状态管理方案(props、react的context、vue的eventbus),或是第三方的全局状态管理方案(redux、vuex、mobx等),都没有脱离那两种实现状态管理的方式:提供修改状态的api或者对状态对象做一层响应式代理。也没有脱离状态管理的两层含义:对状态变化前的异步过程做管理,状态变化后做联动处理。只不过它们用在了不同的地方(前端框架内、全局状态管理库),提供了不同的封装形式(对象、函数),基于不同的思想(函数式、面向对象)结合了不同的异步管理方案(rxjs、generator自定义执行器)。 所以,状态就是数据的变化。前端应用的核心问题就是管理状态,管理状态变化之前的通过视图或者其他方式触发的异步过程,管理状态变化之后的联动渲染和联动的逻辑执行。 虽然我们会用不同的前端框架,不同的全局状态管理库,结合不同的异步过程处理方案,但是思想都是一样的。 毫不夸张地说,理解了状态管理,就理解了前端开发的核心