状态管理是前端整天遇到的概念,但是大家是否思考过什么是状态,管理的又是什么呢?
我们知道,程序是处理数据的,数据是信息的载体,比如颜色是红色或蓝色这就是数据。
那为什么不叫数据管理呢?状态和数据是什么关系?
什么是状态
状态是数据的变化,比如颜色是红色或蓝色是数据,而颜色从红色变为蓝色这就是状态了。
状态的改变对应着视图的渲染或者某段逻辑的执行。比如颜色从红色变为蓝色可能就要重新渲染视图,并且执行发送请求到服务端的逻辑。
通过视图交互或者其他方式触发状态的变化,状态变化联动视图的渲染和逻辑的执行,这就是前端应用的核心。
为什么之前 jQuery 时代没咋听说状态管理的概念,而 Vue、React 时代经常听到呢?
jQuery 时代是手动把数据渲染到视图和执行数据变化之后的逻辑的,它可能没有明确的状态这一层,而是直接把数据渲染成 dom,下次需要数据也是从 dom 来取的。
而 Vue、React 前端框架的时代不需要手动操作 dom 和执行数据变化之后的逻辑,只要管理好状态,由前端框架负责状态变化之后的处理。
状态管理管理的是什么呢?
什么是状态管理
状态管理具体有两层含义:
状态变化之前的逻辑,一般是异步的。
状态变化之后的联动处理,比如渲染视图或执行某段逻辑。
比如 React 的 setState 不会马上修改状态,而是异步的批量的执行,把状态做一下合并。
比如 Redux 的 action 在修改全局 state 之前也是要经历中间件的处理的。
这些都是状态变化之前的异步过程的管理,是状态管理的第一层含义。
再比如 React setState 修改了状态之后要触发视图的渲染和生命周期函数的执行,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 内部去把这些 diff state 更新到 state)。