什么是computed
computed 是组件的计算属性,它的含义是依赖于其他状态而生成的状态。
computed的使用方法
computed 有两种创建方式
- 给
computed函数传递一个getter方法创建 immutable reactive ref object
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value = 3 // error,因为plusOne是immutable ref obj
- 给
computed函数传递一个有get和set方法的对象来创建一个 writable ref object
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缓存值的原理
computed 的特性就在于能够缓存计算的值(提升性能),只有当 computed 的依赖发生变化时才会重新计算,否则读取 computed 的值则一直是之前的值。
创建一个computed
创建 computed 需要调用 computed 函数,computed 函数接收一个 getter 方法或者是一个含有 get 方法和 set 方法的对象,并返回一个 ref 对象。
以下是 computed 工厂函数的全部代码:
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>
) {
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
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
产生ref对象
通过上面的代码发现,Vue 通过执行构造方法ComputedRefImpl来创建ref对象,computed 函数返回的ref对象其实就是执行构造方法 ComputedRefImpl 而创建的一个实例。那么能让computed 缓存值的功能一定在ComputedRefImpl 的内部逻辑里了。
ComputedRefImpl 的全部代码:
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
) {
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
if (self._dirty) {
self._value = this.effect()
self._dirty = false
}
track(self, TrackOpTypes.GET, 'value')
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
ComputedRefImpl 的构造方法一共做了两件事:
- 调用
effect方法生成watcher监听函数并赋值给实例的 effect 属性,(effect方法来自于reactivity/effect.ts) - 设置ref对象是否为
readonly
getter 方法的执行时机
声明一个 computed 时其实并不会执行getter方法,只有在读取 computed 值时才会执行它的 getter 方法,那么接下来我们就要关注 ComputedRefImpl 的 getter 方法。
getter 方法会在读取 computed 值的时候执行,而在 getter 方法中有一个叫 _dirty 的变量,它的意思是代表脏数据的开关,默认初始化时 _dirty 被设为 true ,在 getter 方法中表示开关打开,需要计算一遍computed 的值,然后关闭开关,之后再获取 computed 的值时由于 _dirty 是 false 就不会重新计算。这就是 computed 缓存值的实现原理。
computed重新计算值
在某个地方将 _dirty 的值设为 true ,获取 computed 的值的时候不就会重新计算了。就是在构造函数里。effect 函数会给对象的响应式对象生成监听函数,并对 scheduler 进行了设置。所以最终的流程就是:computed 内部依赖的状态发生改变,执行对应的监听函数,这其中自然会执行 scheduler 里的操作。而在 scheduler 中将 _dirty 设为了 true 。
computed是怎么知道内部依赖产生了变化的?由于在第一次获取computed值(即执行getter方法)的时候对内部依赖进行了访问,在那个时候就对其进行了依赖收集操作,所以computed能够知道内部依赖产生了变化。