computed源码浅析
前面通过对reactive的阅读了解了一下响应式系统的概貌。现在开始对computed进行一定的了解,学习和理解这其中的思路。
在vue3中computed以composition api的形式暴露出来,因此可以直接通过computed方法作为入口去阅读理解其中的思路。
// computed.ts
// 函数重载
export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
// 这个方法内容很简单就是将传入的函数或者option对象,转换成computed的getter、setter
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 返回一个 ComputedRefImpl 实例作为计算属性
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
class ComputedRefImpl<T> {
private _value!: T
private _dirty = true
public readonly effect: ReactiveEffect<T>
// computed在vue3被定义为ref值,在模板或嵌套对象中使用时会自动解包
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 将getter传入effect函数,因为闭包的关系,后续在执行 this.effect 方法时会被激活成待收集依赖
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
// 触发computed的依赖
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// 在get中调用this.effect来计算值
// 惰性控制器,只有在所依赖数据改变后才会再次计算
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
// 以computed实例为对象收集依赖,此处收集的是componentEffect,在组件挂载,更新时对vNode进行diff
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
computed中的主要逻辑就是这样。当调用computed api的时候会创建一个ComputedRefImpl实例,而在获取实例的值的时候。实例的getter会调用它的effect方法计算获取得到最终值。而在计算的过程中会因为get vue实例的数据触发数据的getter,此时就会在当前被获取的数据依赖收集器中收集此computed的effec。随后ComputedRefImpl实例会收集componentEffect(组件实例更新方法)作为依赖。
computed数据是具有缓存特性的,只有在所依赖的数据发生改变,触发了所依赖数据的setter。setter触发其收集的依赖,便会执行到ComputedRefImpl构造函数中传入effect的调度方法,触发此ComputedRefImpl实例所收集的所有依赖其中会包括componentEffect及其他依赖此计算属性的数据。