【vue3源码】四、computed源码解析
参考代码版本:vue 3.2.37
官网文档:https://vuejs.org/
计算属性。承受一个getter
函数,并依据getter
函数的返回值返回一个不可变的响应式ref
对象。或者,承受一个具备get
和set
函数的对象,用来创立可写的ref
对象。
文件地位:packages/reactivity/src/computed.ts
应用示例
只读的计算属性:
const count = ref(1)const doubleCount = computed(() => count.value * 2)console.log(doubleCount.value) // 2count.value = 2console.log(doubleCount.value) // 3
可写的计算属性:
const count = ref(1)const plusOne = computed({ get: () => count.value + 1, set: (val) => { count.value = val - 1 }})console.log(plusOne.value) // 2plusOne.value = 1console.log(count.value) // 0
进行调试:
const count = ref(1)const doubleCount = computed(() => count.value * 2, { onTrack(e) { console.log('track') console.log(e) }, onTrigger(e) { console.log('trigger') console.log(e) }})// 触发track监听console.log(doubleCount.value)// 触发trigger监听count.value++
源码
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 = __DEV__ ? () => { console.warn('Write operation failed: computed value is readonly') } : NOOP } else { getter = getterOrOptions.get setter = getterOrOptions.set } const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR) if (__DEV__ && debugOptions && !isSSR) { cRef.effect.onTrack = debugOptions.onTrack cRef.effect.onTrigger = debugOptions.onTrigger } return cRef as any}
computed
接管两个参数,第二参数是依赖收集和触发依赖的钩子函数,只在开发环境中起作用,这里就不做解释了。次要看第一个参数,察看其类型,发现能够传两种参数:一种是一个getter
函数,一种是个蕴含get
、set
的对象。
首先从getterOrOptions
中确定getter
、setter
(如果getterOrOptions
是个function
,阐明computed
是不可写的,所以会将setter
设置为一个空函数),确定好之后,创立一个ComputedRefImpl
实例,并将其返回。
ComputedRefImpl
export class ComputedRefImpl<T> { public dep?: Dep = undefined // 缓存的值 private _value!: T // 在结构器中创立的ReactiveEffect实例 public readonly effect: ReactiveEffect<T> // 标记为一个ref类型 public readonly __v_isRef = true // 只读标识 public readonly [ReactiveFlags.IS_READONLY]: boolean // 是否为脏数据,如果是脏数据须要从新计算 public _dirty = true // 是否可缓存,取决于SSR public _cacheable: boolean 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 } get value() { // computed可能被其余proxy包裹,如readonly(computed(() => foo.bar)),所以要获取this的原始对象 const self = toRaw(this) // 收集依赖 trackRefValue(self) // 如果是脏数据或者是SSR,须要从新计算 if (self._dirty || !self._cacheable) { // _dirty取false,避免依赖不变反复计算 self._dirty = false // 计算 self._value = self.effect.run()! } return self._value } set value(newValue: T) { this._setter(newValue) }}
结构器
ComputedRefImpl
结构器接管四个参数:getter
、setter
、isReadonly
(是否只读)、isSSR
(是否为SSR
)。
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.computed = this // this.effect.active与this._cacheable在SSR中为false this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly}
在结构器中申明了一个ReactiveEffect
,并将getter
和一个调度函数作为参数传入,在调度器中如果_dirty
为false
,会将_dirty
设置为true
,并执行triggerRefValue
函数。
triggerRefValue
能够承受两个值:ref
、newVal
。
export function triggerRefValue(ref: RefBase<any>, newVal?: any) { ref = toRaw(ref) if (ref.dep) { if (__DEV__) { triggerEffects(ref.dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } }}
triggerRefValue
中首先获取ref
的原始对象,如果ref
的原始对象中有dep
属性,则触发dep
中的依赖。
在初始化effect
之后,会将这个effect
赋给ComputedRefImpl
实例的effect
属性,并将effect.computed
指向ComputedRefImpl
实例
value取值函数
get value() { // computed可能被其余proxy包裹,如readonly(computed(() => foo.bar)),所以要获取this的原始对象 const self = toRaw(this) // 收集依赖 trackRefValue(self) // 如果是脏数据,须要从新计算 if (self._dirty || !self._cacheable) { // _dirty取false,避免依赖不变反复计算 self._dirty = false // 计算 self._value = self.effect.run()! } return self._value}
当读取ComputedRefImpl
实例的value
属性时,因为计算属性可能被其余proxy包裹,所以须要应用toRaw
获取其原始对象。
const self = toRaw(this)
而后调用trackRefValue
进行依赖的收集。
export function trackRefValue(ref: RefBase<any>) { // 如果容许收集并且存在activeEffect进行依赖收集 if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { trackEffects(ref.dep || (ref.dep = createDep()), { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep || (ref.dep = createDep())) } }}
接着依据_dirty
与_cacheable
属性来决定是否须要批改self._value
,其中_dirty
示意是否为脏数据,_cacheable
示意是否能够缓存(取决于是否为服务端渲染,如果为服务端渲染则不能够缓存)。如果是脏数据或不能够被缓存,那么会将_dirty
设置为false
,并调用self.effect.run()
,批改self._value
。
if (self._dirty || !self._cacheable) { self._dirty = false self._value = self.effect.run()!}
最初返回self._value
return self._value
value存值函数
set value(newValue: T) { this._setter(newValue)}
当批改ComputedRefImpl
实例的value
属性时,会调用实例的_setter
函数。
到此,你会发现computed
是懈怠的,只有应用到computed
的返回后果,能力触发相干计算。
为了加深对computed
的了解,接下来以一个例子剖析computed
的缓存及计算过程:
const value = reactive({ foo: 1 })const cValue = computed(() => value.foo)console.log(cValue.value) // 1value.foo = 2console.log(cValue.value) // 2
当打印cValue.value
时,会命中ComputedRefImpl
对应的get
办法,在get
中,执行trackRefValue
收集对应依赖(因为此时没有处于沉闷状态的effect
,即activeEffect
,所以并不会进行依赖的收集),默认_dirty
为true
,将_dirty
设置为false
,并执行effect.run
,计算数据,计算实现后将数据缓存至selft._vlaue
中,不便下次的利用。在调用effect.run
过程中,会将在ComputedRefImpl
结构器中创立的ReactiveEffect
实例收集到targetMap[toRaw(value)].foo
中。
当批改value.foo = 2
,触发targetMap[toRaw(value)].foo
中的依赖,因为在初始化ReactiveEffect
时,设置了一个调度器,所以在触发依赖过程中会执行这个调度器。这个调度器中会判断如果_dirty===false
,则将_dirty
设置为true
,并手动调用triggerRefValue
触发依赖,在调用triggerRefValue
的过程中,因为cValue.dep=undefined
,所以没有依赖要触发。
当第二次打印cValue.value
时,因为_dirty
为true
,所以会执行cValue.effect.run
,并将后果赋值给cValue._value
,最初返回cValue._value
,打印后果2
总结
computed
实质也是个ref
(ComputedRefImpl
),它是懈怠的,如果不应用计算属性,那么是不会进行计算的,只有应用它,才会调用计算属性中的effect.run
办法进行计算,同时将后果缓存到_value
中。
computed
如何从新计算?
首先在第一次获取计算属性的值的过程中会进行依赖的收集,假如计算属性的计算与响应式对象的a、b两个属性无关,那么会将computed
中生成的ReactiveEffect
实例收集到targetMap[obj].a
、targetMap[obj].b
中,一旦a
或b
属性变动了,会触发依赖,而在依赖的触发过程中会执行调度函数,在调度函数中会将脏数据的标识_dirty
设置为true
,并触发计算属性的依赖。那么在下一次应用到计算属性的话,因为_dirty
为true
,便会调用计算属性中的effect.run
办法从新计算值。