前言
ReactiveEffect 作为 vue3 响应式对象中的订阅者,他可以订阅响应式对象的变化并做出对应的变化
ReactiveEffect 对象会在这几种场景下创建:
- computed(接受一个 getter 函数,返回一个只读的响应式 ref 对象,即 getter 函数的返回值。它也可以接受一个带有
get和set函数的对象来创建一个可写的 ref 对象。) - watch (侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数)
- watchEffect (立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。)
- render (页面渲染)
注意:本文只分析 computed ReactiveEffect 的实现原理,由于 computed ReactiveEffect 比较长后文我会用
ReactiveEffect来替代computed ReactiveEffect。
ReactiveEffect
class ReactiveEffect<T = any> {
// 是否活跃
active = true
// dep 数组,在响应式对象收集依赖时也会将对应的依赖项添加到这个数组中
deps: Dep[] = []
// 上一个 ReactiveEffect 的实例
parent: ReactiveEffect | undefined = undefined
// 创建后可能会附加的属性,如果是 computed 则指向 ComputedRefImpl
computed?: ComputedRefImpl<T>
// 是否允许递归,会被外部更改,
allowRecurse?: boolean
// 延迟停止
private deferStop?: boolean
// 停止事件
onStop?: () => void
// dev only
onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void
constructor(
// 参数赋值给fn
public fn: () => T,
// 参数赋值给 scheduler
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
// 标记作用域
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
// 存储最上层 ReactiveEffect 对象
let parent: ReactiveEffect | undefined = activeEffect
// 缓存 是否可以跟踪依赖 上一次的结果
let lastShouldTrack = shouldTrack
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 父结点指向上一个 ReactiveEffect
this.parent = activeEffect
// 当前活跃的 ReactiveEffect
activeEffect = this
// 允许追踪依赖
shouldTrack = true
// 定义当前的 ReactiveEffect 层级
trackOpBit = 1 << ++effectTrackDepth
// 当前层级没超过最大层级限制
if (effectTrackDepth <= maxMarkerBits) {
// 初始化 ReactiveEffect 对应的 Dep 集合 标记
initDepMarkers(this)
} else {
// 清除副作用,一般不会触发
cleanupEffect(this)
}
// 执行构造函数传入的方法
return this.fn()
} finally {
// 当前层级没超过最大层级限制,清空标记
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
// 回到上一层 ReactiveEffect
trackOpBit = 1 << --effectTrackDepth
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
// 延迟停止
if (this.deferStop) {
this.stop()
}
}
}
stop() {
// stopped while running itself - defer the cleanup
if (activeEffect === this) {
this.deferStop = true
} else if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
上面代码注释对 ReactiveEffect 的变量做了一些说明,我们再介绍下其中比较关键的变量:
- trackOpBit: 一个二进制变量,表示当前 ReactivEffect 的层级。
- 在 ReactiveEffect 嵌套执行时产生,比如在 computed 中执行 computed,就会产生嵌套层级。
- effectTrackDepth: 表示 trackOpBit 右移的位数。
- 每产生一个嵌套层级就 +1 等到对应的嵌套层级执行完后就会回到上个层级的标记数。
- activeEffect 当前活跃的副作用对象。
- 会在 ref、reactive、computed 收集依赖时作为依赖项添加到对应的dep集合中。
computed
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
return cRef as any
}
export class ComputedRefImpl<T> {
// dep集合,用来存储依赖向 ReactiveEffect
public dep?: Dep = undefined
// computed 值的引用
private _value!: T
// computed 内部创建的 ReactiveEffect 对象。
public readonly effect: ReactiveEffect<T>
// 标记
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
public _dirty = true
// 默认为 true 不讨论服务端渲染场景
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
// 创建 ReactiveEffect 实例
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
// 设置内部创建的 ReactiveEffect 对象 computed 属性
this.effect.computed = this
// 我们不讨论服务端渲染,这里为true
this.effect.active = this._cacheable = !isSSR
// 不显示传递 setter 就是只读
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
// 获取原始对象
const self = toRaw(this)
// 收集依赖
trackRefValue(self)
// 首次触发get,会进入这个判断,并调用 ReactiveEffect 对象的 run 方法。
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
}
// 返回对应的值
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
从上面代码可以看出,computed 核心在于 class ComputedRefImpl,下面开始分析这个 class
ComputedRefImpl 内部结构:
-
属性
- publuc dep: 用来收集引用了这个computed 的ReactiveEffect 集合。
- private _value: computed 实际的值。
- public readonly effect: computed 创建的 ReactiveEffect 对象
- public readonly __v_isReadonly: 是否只读。
- public _dirty 脏检查标志,在 computed 依赖的响应式数据发生变化更新这个标记、
- public _cacheable 是否缓存结果。
-
构造函数:
- 创建 ReactiveEffect 对象并赋值到 effect 属性上。
- ReactiveEffect
-
get value
- 调用 trackRefValue 收集正在引用 computed.value 的 ReactiveEffect。
- 调用 self.effect.run() 更新全局标记,然后调用 getter(computed 的回调函数) 触发内部响应式变量的getter,将 ReactiveEffect 添加到对应依赖集合中。
- 返回 _value 得到结果
业务代码分析
<template>
<h1>{{ b }}</h1>
<button @click="onClick">按钮</button>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
const a = ref(1);
const b = computed(() => a.value);
const onClick = () => {
a.value++;
};
</script>
简单介绍一下上面代码的关键逻辑:
- 变量 a 是一个 ref 响应式对象。
- 变量 b 是一个 computed 对象,它也具有响应式对象的特性。
- 当 a.value 变化时,b 会响应 a.value 的变化计算出新的结果。
我们从初始化,点击按钮,重新渲染这三个阶段来分析 ComputedRefImpl 的内部变化:
初始化:
- 调用 computed 实例化 ComputedRefImpl,然后创建对应 ReactiveEffect 对象。
- template 中引用了 b ,触发 get value ,触发 ComputedRefImpl 的依赖收集逻辑。
- 执行 effect.run(), 然后调用传入的回调函数,春如的回调函数中有引用 a.value,触发 a 的依赖收集,将 ReactiveEffect 添加到 a 的 dep 集合中。
点击按钮:
- a.value 自增,接着通知 a 中的 dep 集合所有依赖响应它的变化,这个集合中包含 b 创建 ReactiveEffect。 2.调用 ReactiveEffect scheduler,这个方法里面包含了 b 通知依赖更新的逻辑,所以这时也会通知 b 的依赖者响应变化。
重新渲染:
- template 中引用了 b,触发 get value 随后将 render ReactiveEffect 添加到 b 的 dep 集合中,然后执行 effect.run()更新 内部 _value 的值。
依赖收集有去重的逻辑,因为去重逻辑不影响我们分析,所以我在上面忽略了这一过程
总结
ReactiveEffect 与响应式对象的交同上面三个阶段的描述所示,这个过程逻辑也是非常清晰的,关于 computed ReactiveEffect 分析就到这儿了,如果我在文中的表述有不恰当或者不准确的地方,请各位掘友不吝赐教。