Open CommanderXL opened 9 months ago
到位。
我这么理解对不对 ,就是先 pauseTracking 跑一遍 getter 看数据变没变,没变的话后边的 effect 就不用跑了。
@JasKang 嗯你的理解是对的。dirty check 如果没有发生变化的话,后续 effect 的代码就不需要执行了。
const effect = new ReactiveEffect(fn, () => {}, () => {
if (effect.dirty) {
effect.run()
}
})
这个逻辑在响应式系统里还挺常见的,思路是在执行具体的effect时前置判断一下依赖是否真的发生了变化。
angular的signal和mobx是通过维护值版本来搞的,推荐下面两个文档,对响应式系统的常见问题和解法做了详细介绍。
https://en.wikipedia.org/wiki/Reactive_programming
对于一个表格里的公式单元格,其实也应用到类似思路,A -> B -> C。当C的内容发生变化时,A和B被标脏,在计算A时,可以前置判断B是否真的发生了变化,如果没有变化,是不需要重新计算值/执行副作用的。
@githubxiaowen 感谢分享
框架现状
在2023.12.28日刚发布的 Vue3.4 版本当中重构了部分响应式系统的功能。博客当中举了一个例子:
在之前的版本当中,
count.value
发生变化的话,但是isEven.value
不一定真正的发生了变化,但是仍然会再次触发watchEffect
的执行。主要的原因还是在于之前的 computed effect 的设计,computed 依赖的响应式数据发生了变化之后,computed effect scheduler 会立即触发对其产生依赖的 effect。所以在这个例子当中,count.value 发生了变化,触发 computed effect 进而也就触发了 watch effect 的执行。由这个简单的例子可以继续推导下,在 Vue 框架内部基于 ReactiveEffect 封装了更加上层的响应式 api 的使用场景,包括:
在不同的使用场景下,这些 effect 都可以和 computed 数据建立起依赖关系。
那么不管是以上哪种依赖关系, computed 数据在 re-computed 的过程当中都是可能会出现上述例子当中出现的:computed 数据的值实际没有变化,但是 effect 会重新执行的情况,从而导致了一些不必要的性能损耗。
那么为了优化这种场景,Vue3.4 引入了 effect dirty check 机制:
ReactiveEffect 重构
首先来看下 ReactiveEffect 重构后几个大的变化:
trigger
函数(trigger 和 scheduler 之间的区别:trigger 要比 scheduler 先执行,提前派发一些信号,主要是用以 computed 数据的处理)_dirtyLevel
用以标记当前 effect 实例的 dirty 状态(区分了 computed 数据和普通的 reactive/ref 数据)computed 重构
triggerEffects 重构
再回到在 Blog 当中的例子,看下响应式数据发生变化后整个 effect 依赖关系触发流程重构前后的工作流程:
在优化后的流程当中依据依赖关系触发 effect scheduler 的流程没太大变化,不过在触发 effect 的过程当中新增了对于 effect dirty 状态的更新,尤其是 computed 触发其依赖 effect 会将对应的 dirty 状态更新为
ComputedValueMaybeDirty
,进入到 effect scheduler 调用的流程当中通过对 effect dirty check 来决定是否进行 scheduler 后续的流程(开发者需要手动调用),也就是 effect scheduler 后续的调用。那么对于 effect dirty check 的流程来说,实际也就是看和当前 effect 有依赖关系的 computed 数据是否真的发生了变化(触发 computed value getter 的过程),一旦有一个 computed 数据发生了变化也就会更新 effect dirty 的状态为
ComputedValueDirty
。基于 ReactiveEffect 的上层封装
ReactiveEffect 是
@vue/reactivity
所暴露出最重要最底层的用以搭建整个响应式系统的 api,那么如果要基于 3.4 版本后的 ReactiveEffect 去封装上层的响应式 api 有两点需要注意: