概述
从一个 demo 入手来讲解 computed 的原理, 阅读本文前可先了解 走进Vue3源码:响应式原理(超详细)
Demo
import { reactive, computed, watchEffect } from 'vue'
export default{
setup() {
const state = reactive({ count: 1 })
// 1. 初始化 computed
const doubleCount= computed(() => state.count * 2)
// 2. 触发 doubleCount 的 get, 从而收集 doubleCount 的依赖和 state.count 的依赖
watchEffect(() => console.log(doubleCount.value))
// 输出 2
// 3. 触发 state.count 的 set,从而触发 state.count 的依赖执行和 doubleCount 的依赖执行
state.count ++
// 输出 4
}
1. 初始化 computed
执行代码
const doubleCount= computed(() => state.count * 2)
源码详解:computed.ts & effect.ts
// 1. 执行 computed
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
// 1. 格式化 setter 和 getter, 因为 computed 支持函数和对象两种传参
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 2. 构建 ComputedRef
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
// 2. 生成 ComputedRef
class ComputedRefImpl<T> {
private _value!: T
private _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 1. 生成 effect, effect 可以看做是 demo 中 getter: () => state.count * 2 方法的代理方法,便于处理依赖
// 当该计算属性被作为依赖执行时(demo 中 doubleCount 的 effect 作为 state.count 的依赖),实际是执行 effect,而非原先传入的 getter
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
...
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
// 初始化完成
}
get value() { ... }
set value() { ... }
}
2. 收集计算属性 doubleCount 的依赖和 state.count 的依赖
执行代码
// 触发 doubleCount 的 get
// 将下面的 watchEffect 对应的 effect 添加到 doubleCount 的依赖中
// 也就是说当 doubleCount 的值改变时,watchEffect 对应的 effect 会被执行
watchEffect(() => console.log(doubleCount.value))
源码详解:computed.ts
class ComputedRefImpl<T> {
...
get value() {
// 1. 懒执行获取 doubleCount 的值
if (this._dirty) { // this._dirty 初始值为 true
// 2. 在执行下面 this.effect() 的同时会触发 state.count 的 get
// 因此会将 doubleCount effect 添加到 state.count 的依赖中
this._value = this.effect()
this._dirty = false
}
// 2. 将 watchEffect 的 effect 添加到 doubleCount 的依赖中
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
// 依赖收集结束,此时三者关系: watchEffect(effect) 是 doubleCount 的依赖, doubleCount(effect) 是 state.count 的依赖
}
}
3. 触发 state.count 的依赖执行和 doubleCount 的依赖执行
执行代码
state.count ++
// 触发流程: 修改 state.count => 执行 doubleCount 的 effect => 执行 watchEffect 的 effct
源码详解:computed.ts
// 1. 触发 state.count 的 set,从而 state.count 的依赖会被执行,即 doubleCount 的 effect
class ComputedRefImpl<T> {
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 接上面步骤 1
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
// 2. 执行 doubleCount 的 effect,实际上是执行 scheduler 方法,具体实现在 trigger 方法中
if (!this._dirty) {
// 2.1 标记为脏数据,表示需要更新
this._dirty = true
// 2.2 触发 doubleCount 的依赖执行,即 watchEffect 的 effect
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
// 3. watchEffect 对应的 effct 再次执行,并触发 doubleCount 的 get
get value() {
// 3.1 再次更新 doubleCount 的值,依旧是懒执行,在获取值时才会更新,有利于优化性能
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
// 3.2 再次收集依赖不会生效,因为已收集过了
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
}
总结
以上就是 computed 的实现原理及处理流程。
需要理清楚 demo 中 watchEffect, doubleCount, state.count 三者的关系:
watchEffect (依赖 =>) doubleCount (依赖 =>) state.count
修改 state.count (=> 触发) doubleCount 对应的 effect (=> 触发) watchEffect 对应的 effect