持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
前言
Computed 方法有两种用法,一个是传入一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象;另外一个是接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。
使用
只传入 getter,生成的 ref 对象 refCount 为只读。
import { ref, computed } from 'vue'
const count = ref(1)
const refCount = computed(() => count.value + 1)
console.log(refCount.value) // 2
refCount.value++ // 错误
接受一个具有 get 和 set 函数的对象,赋值时触发 set
import { ref, computed } from 'vue'
const count = ref(1)
const refCount = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
refCount.value = 1
console.log(count.value) // 0
源码
computed 方法的位置在 packages/reactivity/src/computed.ts
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
- 如果是对象,那么把对象的 get 和 set 分别传给 getter 和 setter
- 把getter 和 setter传给 ComputedRefImpl 类,实例化出ref对象
- 最后返回 ref 对象
ComputedRefImpl 类
我们先看下这个类的成员变量
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
public _dirty = true
public _cacheable: boolean
}
- 私有变量 _value 保存数据
- dep 保存依赖
- effect 一个只读的副作用函数
- 在调度器中判断 _dirty 是否为 false,如果是的话,会利用 trigger 派发更新。
再来看下这个类的构造函数
export class ComputedRefImpl<T> {
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
}
创建一个 effect 对象,传入getter 和 一个调度器,这个调度器,会在使用 triggerEffect 进行派发更新时调用,这个调度器判断 _dirty 是否为false, 是的话,把 _dirty 恢复成 true,并进行派发更新,这个 triggerRefValue 最终会执行 triggerEffect。
然后把当前对象赋值给 this.effect
export class ComputedRefImpl<T> {
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
trackRefValue(self)
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
通过 getter 函数获取值时,会先执行副作用函数,并将副作用函数的返回值赋值给 _value,并将 _dirty 的值赋值给 false,这就可以保证如果 computed 中的依赖没有发生变化,则副作用函数不会再次执行,那么在 getter 时获取到的 _dirty 始终是 false,也不需要再次执行副作用函数,节约开销。之后通过 track 收集依赖,并返回 _value 的值。
在 setter 中直接执行我们传入的 set 函数。
总结
总的来说,核心依然是通过 effect 进行依赖收集和派发更新,区别就是在 getter 和 setter 是调用了用户传进去的方法。