在 Vue 3 中, computed 是一个用于定义和计算响应式计算属性的函数。它可以根据依赖的响应式数据进行自动更新,并且具有缓存机制,只有当依赖的数据发生变化时,才会重新计算计算属性的值。使用computd函数定义计算属性时,需要传入一个计算函数,该函数返回计算属性的值。Vue 3 内部会创建一个专门用于计算属性的响应式对象,并自动追踪其依赖关系,当依赖的数据发生变化时,会重新计算计算属性的值。
使用案例
ini
const count = ref(1);
const plusOne = computed(() => count.value + 1);
console.log(plusOne.value); // 2
count.value++;
console.log(plusOne.value); // 3
除了给 computed传入一个回调函数以外,你还可以传入一个包含get、set函数的对象,如下面的例子:
ini
const count = ref(1);
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1;
},
});
plusOne.value = 1;
console.log(count.value); // 0
在这个例子中,我们可以看到,computed 函数接收了一个包含 getter 和 setter 函数的对象。
getter 函数与之前相同,返回 count.value + 1。
值得注意的是 setter 函数:当我们修改 plusOne.value 的值时,就会触发 setter 函数。
实际上,setter 函数内部会根据传入的参数来修改计算属性所依赖的值 count.value。
一旦依赖的值发生变化,再次获取计算属性时就会重新执行 getter 函数,因此获取到的值也会随之改变。
computed实现原理如下:
computed函数实现
ini
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
}
- 首先声明了两个变量getter和setter
- onlyGetter变量用于判断getterOrOptions是否仅仅包含getter函数。这是通过调用isFunction函数实现的。
- 如果onlyGetter为真,即getterOrOptions仅包含一个函数,那么将这个函数赋值给getter,setter则被设置为NOOP,即不执行任何操作。
- 如果onlyGetter为 false,即getterOrOptions包含get和set属性,那么分别将getterOrOptions的get和set属性赋值给getter和setter。
ComputedRefImpl源码实现
kotlin
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean = false
public _cacheable: boolean
/**
* Dev only
*/
_warnRecursive?: boolean
constructor(
private getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean,
) {
// 将用户的getter放到effect中,这样能对getter函数进行依赖收集,activeEffect会变为getter生成的effect
// 传入scheduler调用函数,稍后 依赖的属性变化会调用此方法
this.effect = new ReactiveEffect(
() => getter(this._value),
() =>
// 稍后依赖属性变化会执行此调度函数
triggerRefValue(
this,
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
? DirtyLevels.MaybeDirty_ComputedSideEffect
: DirtyLevels.MaybeDirty,
),
)
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// 获取原始对象
const self = toRaw(this)
// 第一次是DirtyLevels.Dirty,开关开启,说明是脏值,执行函数,然后关闭开关
if (
(!self._cacheable || self.effect.dirty) &&
hasChanged(self._value, (self._value = self.effect.run()!))
) {
triggerRefValue(self, DirtyLevels.Dirty)
}
// // 如果当前在effect中访问了计算属性,计算属性可以收集这个effect
trackRefValue(self)
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
get _dirty() {
return this.effect.dirty
}
set _dirty(v) {
this.effect.dirty = v
}
// #endregion
}
在上面的源码中,拆解实现步骤如下:
- 通过computed函数进入,getter即定义computed时的入参,创建ComputedRefImpl实例的时候也会创建一个ReactiveEffect实例(计算属性effect),后续执行render的过程也会创建响应式的ReactiveEffect实例(响应式effect)
- 当访问plusOne.value时,触发get value,run()函数指向的是computed实例的getter也是我们定义computed是入参的函数,此函数执行的时候会获取计算属性的结果
- 执行trackEffect函数(入参的activeEffect是 响应式effect),把 响应式effect放在ref.dep中(ref即computed)(看上一章节同ref的依赖收集)
- 执行count.value++的时候触发count实例的set value,拿到count.dep(保存的是计算属性effect),执行effect.trigger()(computed.dep中存的是响应式effect)会把响应式函数update放在队列里后面触发更新,update时通过dirty判断是否需要重新获取