vue3 源码阅读 - computed ReactiveEffect - 掘金

前言

ReactiveEffect 作为 vue3 响应式对象中的订阅者,他可以订阅响应式对象的变化并做出对应的变化

ReactiveEffect 对象会在这几种场景下创建:

  1. computed(接受一个 getter 函数,返回一个只读的响应式 ref 对象,即 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。)
  2. watch (侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数)
  3. watchEffect (立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。)
  4. render (页面渲染)
注意:本文只分析 computed ReactiveEffect 的实现原理,由于 computed ReactiveEffect 比较长后文我会用 ReactiveEffect 来替代 computed ReactiveEffect

ReactiveEffect

typescript

复制代码

`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

typescript

复制代码

`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 是否缓存结果。
  • 构造函数:

    1. 创建 ReactiveEffect 对象并赋值到 effect 属性上。
    2. ReactiveEffect
  • get value

    1. 调用 trackRefValue 收集正在引用 computed.value 的 ReactiveEffect。
    2. 调用 self.effect.run() 更新全局标记,然后调用 getter(computed 的回调函数) 触发内部响应式变量的getter,将 ReactiveEffect 添加到对应依赖集合中。
    3. 返回 _value 得到结果

业务代码分析

vue

复制代码

`<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 的内部变化:

初始化:

  1. 调用 computed 实例化 ComputedRefImpl,然后创建对应 ReactiveEffect 对象。
  2. template 中引用了 b ,触发 get value ,触发 ComputedRefImpl 的依赖收集逻辑。
  3. 执行 effect.run(), 然后调用传入的回调函数,春如的回调函数中有引用 a.value,触发 a 的依赖收集,将 ReactiveEffect 添加到 a 的 dep 集合中。

点击按钮:

  1. a.value 自增,接着通知 a 中的 dep 集合所有依赖响应它的变化,这个集合中包含 b 创建 ReactiveEffect。 2.调用 ReactiveEffect scheduler,这个方法里面包含了 b 通知依赖更新的逻辑,所以这时也会通知 b 的依赖者响应变化。

重新渲染:

  1. template 中引用了 b,触发 get value 随后将 render ReactiveEffect 添加到 b 的 dep 集合中,然后执行 effect.run()更新 内部 _value 的值。

依赖收集有去重的逻辑,因为去重逻辑不影响我们分析,所以我在上面忽略了这一过程

总结

ReactiveEffect 与响应式对象的交同上面三个阶段的描述所示,这个过程逻辑也是非常清晰的,关于 computed ReactiveEffect 分析就到这儿了,如果我在文中的表述有不恰当或者不准确的地方,请各位掘友不吝赐教。


原网址: 访问
创建于: 2023-10-12 15:39:36
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论