Vue3源码解析(computed-计算属性)
作者:秦志英
前⾔
上⼀篇⽂章中我们分析了Vue3响应式的整个流程,本篇⽂章我们将分析Vue3中的computed计算属性是如何实现的。
在Vue2中我们已经对计算属性了解的很清楚了,在Vue3中提供了⼀个computed的函数作为计算属性的API,下⾯我们来通过源码的⾓度去分析计算属性的运⾏流程。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 = NOOP
} else {
getter =
setter = getterOrOptions.set
}
vue中reactive
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
在最开始使⽤函数重载的⽅式允许computed函数接受两种类型的参数:第⼀种是⼀个getter函数, 第⼆种是⼀个带get和set的对象。
接下就是在函数内部根据传⼊的不同类型的参数初始化函数内部的getter和setter函数,如果传⼊的是⼀个函数类型的参数,那么getter就是这个函数,setter就是⼀个空的操作,如果传⼊的参数是⼀个对象,则getter就等于这个对象的get函数,setter就等于这个对象的set函数。
在函数的结尾返回了⼀个new ComputedRefImpl,并将前⾯我们标准化后的参数传递给了这个构造函数。
下⾯我们就来分析⼀下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
) {
// 对传⼊的getter函数进⾏包装
this.effect = effect(getter, {
lazy: true,
// 调度执⾏
scheduler: () => {
if (!this._dirty) {
this._dirty = true
/
/ 派发通知
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
}
// 访问计算属性的时候默认调⽤此时的get函数
get value() {
// 是否需要重新计算
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
// 访问的时候进⾏依赖收集此时收集的是访问这个计算属性的副作⽤函数
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
ComputedRefImpl类在内部维护了_value和_dirty这两个⾮常重要的私有属性,其中_value使⽤⽤来缓存我们计算的结果,_dirty是⽤来控制是否需要重现计算。接下来我们来看⼀下这个函数的内部运⾏机制。
⾸先构造函数在初始化的时候使⽤了effect函数对传⼊getter进⾏了⼀层包装(上⼀篇⽂章中我们分析过effect函数的作⽤就是将传⼊的函数变成可响应式的副作⽤函数),但是这⾥我们在effect中传⼊了⼀些配置参数,还记得前⾯我们分析trigger函数的时候有这⼀段代码:
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
当属性值发⽣改变之后,会触发trigger函数进⾏派发更新,将所有依赖这个属性的effect函数循环遍历,
使⽤run函数执⾏effect,如果effect的参数中配置了scheduler,则就执
⾏scheduler函数,⽽不是执⾏依赖的副作⽤函数。当计算属性依赖的属性发⽣变化的时候,回执⾏包装getter函数的effect, 但是因为配置了scheduler函数,所以真正执⾏的
是scheduler函数,在scheduler函数中并没有执⾏计算属性的getter函数求取新值,⽽是将_dirty设置为false,然后通知依赖计算属性的副作⽤函数进⾏更新, 当依赖计算属性的副作⽤函数收到通知的时候就会访问计算属性的get函数,此时会根据_dirty值来确定是否需要重新计算。
回到我们的这个构造函数中,只需要记得我们在构造函数初始化三个重要的点:第⼀:对传⼊的getter函数使⽤effect函数进⾏包装。第⼆:在使⽤effect包装的过程中,我们会执⾏getter函数,此时getter函数执⾏过程中对于访问到的属性会将当前的这个计算属性收集到对应的依赖集合中, 第三:传⼊了配置参数lazy和scheduler,这些配置参数在当前的这个计算属性所订阅的属性发⽣改变的时候,⽤来控制计算属性的调度时机。
接着我们继续分析get value,当我们访问计算属性的值时候实际上访问的就是这个函数的返回值, 它会根据_dirty的值来判断是否需要重新计算getter函数,_dirty为true需要重新
执⾏effect函数,并将effect的值置为false,否则就返回之前缓存的_value值。在访问计算属性值的阶段会调⽤track函数进⾏依赖收集,此时收集的是访问计算属性值的副作⽤函数, key始终是vlaue。
最后就是当设置计算属性的值的时候会执⾏set函数,然后调⽤我们传⼊的_setter函数。
⽰例流程
⾄此计算属性的执⾏流程就分析完毕了,我们来结合⼀个⽰例来完整的过⼀遍整个流程:
<template>
<div>
<button @click="addNum">add</button>
<p>计算属性:{{computedData}}</p>
</div>
</template>
<script>
import { ref, watch,reactive, computed } from 'vue'
import { effect } from '@vue/reactivity'
export default {
name: 'App',
setup(){
const testData = ref(1)
const computedData = computed(() => {
return testData.value++
})
function addNum(){
testData.value += 10
}
return {
addNum,
computedData
}
},
}
</script>
下⾯是⼀张流程图,当点击页⾯中的按钮改变testData的value值时,发⽣的变化流程就是下⾯的红线部分。
⾸先初始化页⾯的时候,testData经过ref()之后变成响应式数据,会对访问testData.value的值进⾏依赖收集,当testData.value的值发⽣变化的话,会对依赖这个值的依赖集合进⾏派发更新
computed中传⼊了⼀个getter函数,getter函数内部有对testData.value的访问,此时当前的这个计算属性
的副作⽤函数就订阅了testData.value的值,computed返回了⼀个值,⽽页⾯中的组件有对computed返回值的访问,页⾯的渲染副作⽤函数就订阅了computed的返回值,所以这个页⾯中有两个依赖集合。
当我们点击页⾯中的按钮,会改变testData.value的值,此时会通知订阅计算属性的副作⽤函数进⾏更新操作,由于我们在⽣成计算属性副作⽤的时候配置了scheduler,所以执⾏的是scheduler函数,scheduler函数并没有⽴即执⾏getter函数进⾏重新计算,⽽是将ComputedRefImpl类内部的私有变量_dirty设置为true,然后通知订阅当前计算属性的副作⽤函数进⾏更新操作。
组件中的渲染副作⽤函数执⾏更新操作的时候会访问到get value函数,函数内部会根据_dirty值来判断是否需要重新计算,由于前⾯的scheduler函数将_dirty设置为true所以此时会调⽤getter函数的副作⽤函数effect,这个时候才会重新计算并将结果返回,页⾯数据更新。
总结
计算属性两个最⼤的特点就是
延时计算计算属性所依赖的值发⽣改变的时候并不会⽴即执⾏getter函数去重新计算新的结果,⽽是打开重新计算的开关并通知订阅计算属性的副作⽤函数进⾏更新。如果当前的计算属性没有依赖集合就不执⾏重新计算逻辑,如果有依赖触发计算属性的get,这个时候才会调⽤this.effect()进⾏重新计算。
缓存结果当依赖的属性没有发⽣改变的,访问计算属性会返回之前缓存在_value中的值。
对 Electron 感兴趣?请关注我们的开源项⽬,带你极速上⼿ Electron。
我们每周五会精选⼀些有意思的⽂章和消息和⼤家分享,来掘⾦关注我们的。
我们是好未来 · 晓⿊板前端技术团队。
我们会经常与⼤家分享最新最酷的⾏业技术知识。
欢迎来、、、、、、关注我们。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。