当前位置: 首页 > 科技观察

了解了状态管理,就明白了前端开发的核心

时间:2023-03-13 17:07:06 科技观察

状态管理是前端整天碰到的概念,但是你有没有想过什么是状态,什么是管理?我们知道程序是处理数据的,数据是信息的载体,比如颜色是红色或者蓝色,这就是数据。那为什么不叫它数据管理呢?状态和数据是什么关系?什么是状态?状态变化对应于视图的渲染或者某个逻辑的执行。例如,如果颜色从红色变为蓝色,则可能需要重新渲染视图并执行向服务器发送请求的逻辑。状态变化由视图交互或其他方式触发,状态变化与视图的渲染和逻辑的执行挂钩,是前端应用的核心。为什么在jQuery时代没有听说过状态管理的概念,而在Vue和React时代却经常听到呢?在jQuery时代,数据是手动渲染到视图上,数据变化后的逻辑被执行。它可能没有明确的状态层,而是直接将数据渲染到dom中,下次需要数据时,也是从dom中取。在Vue和React前端框架时代,数据变化后不需要手动操作dom和执行逻辑。只要状态管理好,前端框架负责状态变化后的处理。状态管理管理什么?什么是状态管理?状态管理有两层意思:状态变化前的逻辑一般是异步的。状态变化后的联动处理,比如渲染一个视图或者执行某个逻辑。比如React的setState并不是立即修改状态,而是异步的批量执行,合并状态。比如Redux的action在修改全局状态之前也需要经过中间件处理。这些都是状态变化前对异步进程的管理,这是状态管理的第一个意义。再比如ReactsetState修改状态后,需要触发视图的渲染和生命周期函数的执行。Hooks也会在依赖数组的状态改变后重新执行。(vue的数据修改后,会重新渲染视图,执行computed和watch逻辑。)Redux修改全局状态后,需要通知组件渲染或做其他逻辑处理,如Vuex、Mobx。这些就是状态变化后的联动处理的管理,这是状态管理的第二层含义。我们知道什么是状态,什么是状态管理。前端框架Vue、React和全局状态管理库Redux、Mobx、Vuex是如何实现状态管理的?两种状态管理state的实现思路不会相同,多个状态的集合会用对象的key和value来表示,比如React的state对象和Vue的data对象(虽然名字data也指状态)。如何监听一个对象的变化?能不能提供一个api修改,在这个api里做状态变化前的处理,状态变化后做联动处理。这样的方案只能通过api触发状态修改,直接修改状态无法触发状态管理逻辑。React的setState就是这种思路。通过setState修改状态,会在状态改变前做批量异步状态合并,状态改变后会触发视图渲染、钩子、生命周期的重新执行。但是直接修改状态是没有用的。那么即使直接修改状态,又如何监听到变化呢?可以为state对象做一层proxy,代理它的get和set,在state的get执行时收集依赖state的逻辑。当集合修改状态时,通知所有依赖于它的逻辑(视图渲染、逻辑执行)进行更新。Vue的数据监控变化就是基于这样的思想。状态geting时,依赖封装成一个Watcher,state设置时,通知所有Watcher更新。这种思想叫做反应式,意思是在状态改变后自动响应变化,做联动处理。Proxyget和set可以使用Object.defineProperty的API,但是无法监听动态添加或删除的对象属性,所以Vue3改用Proxy的API实现。监听对象的变化有两种方式:提供api修改,内部联动处理。为对象做一层代理,设置时做联动处理,获取时通知收集到的所有依赖。前端框架状态变化的性能优化,但是频繁修改状态不需要每次都联动处理,有的可以组合,比如两次把颜色变成红色,那么后面的逻辑就不需要了执行两次,需要再做一些性能优化。所以React的setState是异步的,会做批量状态合并(注意React的setState传入的不是最终状态,而是statediff,React内部会将这些diff状态更新为state)。而且因为vue是直接修改的同一个对象,不需要做任何合并。它的Watcher执行是异步的,多次重复已经放入队列的Watcher就可以了。组件之间的状态管理组件内部的状态管理就是这样,使用前端框架自带的状态机制来管理。组件之间呢?一个组件的状态如何与其他组件一起改变?props传递props,将当前组件的状态作为props传递给其他组件,从而实现变化的联动。但是props只能一层层传递。如果联动中组件和你要改变的组件之间有很多层,传递props是很麻烦的。在这种情况下,前端框架也提供了解决方案,React提供了Context,Vue提供了EventBus。Context和EventBusReact组件可以在上下文中存储状态,当上下文中的状态发生变化时,会直接触发关联组件的渲染。Vue可以在一个组件中发出一个事件,然后另一个组件发出这个事件,然后更新自己的数据以触发渲染。但这两个API在Vue3中已被弃用。前端框架自带的任意层组件的状态联动方案只能应对简单的场景。对于复杂的场景,必须使用全局状态管理库,例如Redux、Vuex和Mobx。你为什么这么说?还记得状态管理的两个意思吗?状态改变前异步流程的管理,状态改变后的联动处理。Context和EventBus都只实现状态变化后的联动处理,不支持状态变化前的异步流程管理。例如,如果多个组件需要修改上下文中的值(或者通过事件总线修改全局状态),这个过程必须执行一段异步逻辑来显示加载。多个组件如何重用这个加载逻辑??还有,如果异步过程比较麻烦,需要用rxjs之类的库。如何将上下文和事件总线与rxjs结合起来?当然可以做一些逻辑复用的封装和rxjs对context和eventbus的一些组合scheme之类的封装,但是比较麻烦。而且更重要的是,如果你想这样做,不需要使用上下文和事件总线,只需要使用全局状态管理库。Redux、Mobx、Vuexredux提供了中间件机制。组件向store(存放全局状态的地方)发送一个action,之前会经过一层层的中间件处理。这里可以做一些可重用的逻辑封装,比如加载处理,也可以结合异步进程处理方案rxjs。redux中最常用的中间件是redux-saga和redux-observable,它们都是管理异步进程的。redux-saga是基于生成器实现的。不管是同步还是异步,只需要声明式的描述要执行的逻辑即可。saga里面的executor会做同步或者异步的处理。描述异步逻辑非常简洁,redux-saga提供了很多内置的逻辑封装。redux-observable是结合rxjs的方案,将action变成数据源,经过层层的opreator处理,最后传给store。你可以使用rxjs生态中大量的oprators,将它们组装起来即可,你不需要自己编写异步逻辑的具体实现。Vuex也支持中间件机制。mobx不提供中间件机制。它的action是执行状态类的一个方法,可以用类封装。有同学对这些状态管理库不熟悉,下面简单介绍一下。我们已经明确了状态管理的实现只有两种方案,一种是提供API进行修改,另一种是作为状态对象的响应式代理。前端框架的状态管理是这样的,独立的全局状态管理库也是如此。Redux是一种解决方案,它提供了一个api来修改,通过reducer函数处理传入的action,并返回一个新的状态。而且,redux的思想是一个函数式的思想。每个reducer都是一个纯函数,输入输出一一对应,返回的状态是全新的。为了方便创建新的状态,一般都会配备一个不可变的库,只要修改属性就会返回新的状态对象。mobx是一个响应式代理解决方案。它为全局状态创建了一层代理(通过Object.defineProperty)。state的get收集依赖,设置时触发依赖更新。因此,这种方案可以很自然地将全局状态组织成类,这是一种面向对象的思想,可以通过继承等方式实现逻辑复用。importReact,{Component}from'react';importReactDOMfrom'react-dom';import{observer}from'mobx-react';@observerclassTodoListViewextendsComponent{render(){return

    {this.props.todoList.todos.map(todo=>)}
剩余任务:{this.props.todoList.unfinishedTodoCount}
}}constTodoView=observer(({todo})=>
  • todo.finished=!todo.finished}/>{todo.title}
  • )conststore=newTodoList();ReactDOM.render(,document.getElementById('mount'));vuex就像是两种思想的结合,内部的变化监听是通过响应式代理实现的,但是暴露出来的api是redux动作的集合。如果将它与React一起使用,则需要将组件添加到状态依赖项中。不需要自己调用subscribe之类的API,而是可以直接使用一些封装好的高层组件(接受组件作为参数并返回新组件的组件),比如react-redux的connect,mobx的observer-反应。总结了这么多,回过头来看,你会发现:无论是前端框架内置组件中的状态变化管理方案(react的setState,vue直接修改数据),还是组件间的状态管理前端框架提供的解决方案(props、react的context、vue的eventbus),或者第三方的全局状态管理解决方案(redux、vuex、mobx等),都离不开两种实现状态管理的方式:提供API来修改状态或为状态对象制作一层响应式代理。状态管理的两个含义没有分开:管理状态变化前的异步过程,和状态变化后的联动处理。只是它们用在不同的地方(在前端框架,全局状态管理库),提供不同的封装形式(对象,函数),基于不同的思想(函数式,面向对象)组合不同的异步管理方案(rxjs,生成器+自定义执行器)。所以,状态就是数据的变化。前端应用的核心问题是对状态进行管理,管理状态变化前通过视图或其他方式触发的异步过程,管理状态变化后的联动渲染和联动逻辑执行。虽然我们会使用不同的前端框架,不同的全局状态管理库,结合不同的异步进程处理方案,但是思路是一样的。毫不夸张的说,懂了状态管理,就懂了前端开发的核心。