【vue3源码】4.计算源码分析参考代码版本:vue3.2.37官方文档:https://vuejs.org/computedproperty。接受一个getter函数并根据getter函数的返回值返回一个不可变的反应式ref对象。或者,接受具有get和set函数的对象以创建可写的ref对象。文件位置:packages/reactivity/src/computed.ts使用的只读计算属性示例:constcount=ref(1)constdoubleCount=computed(()=>count.value*2)console.log(doubleCount.value)//2count.value=2console.log(doubleCount.value)//3个可写计算属性:constcount=ref(1)constplusOne=computed({get:()=>count.value+1,set:(val)=>{count.value=val-1}})console.log(plusOne.value)//2plusOne.value=1console.log(count.value)//0用于调试:constcount=ref(1)constdoubleCount=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++源码导出函数computed(getterOrOptions:ComputedGetter|WritableComputedOptions,debugOptions?:DebuggerOptions,isSSR=false){让getter:ComputedGetter让setter:ComputedSetterconstonlyGetter=isFunction(getterOrOptions)if(onlyGetter){getter=getterOrOptionssetter=__DEV__吗?()=>{console.warn('写入操作失败:计算值是只读的')}:NOOP}else{getter=getterOrOptions.getsetter=getterOrOptions.set}constcRef=newComputedRefImpl(getter,setter,onlyGetter||!setter,isSSR)if(__DEV__&&debugOptions&&!isSSR){cRef.effect.onTrack=debugOptions.onTrackcRef.effect.onTrigger=debugOptions.onTrigger}returncRefasany}computed接收两个参数,第二个参数是一个hook依赖收集和触发依赖的函数,只在开发环境下有效,这里不做解释。主要看第一个参数,观察它的类型,发现可以传递两个参数:一个是getter函数,一个是包含get和set的对象。首先从getterOrOptions中判断getter和setter(如果getterOrOptions是一个函数,说明computed是不可写的,所以setter会被设置为一个空函数),确定之后,创建一个ComputedRefImpl实例并返回。ComputedRefImplexportclassComputedRefImpl{publicdep?:Dep=undefined//缓存值private_value!:T//在构造函数中创建的ReactiveEffect实例publicreadonlyeffect:ReactiveEffect//标记为ref类型publicreadonly__v_isRef=true//只读标志publicreadonly[ReactiveFlags.IS_READONLY]:boolean//是否是脏数据,如果是脏数据需要重新计算public_dirty=true//是否可以缓存取决于SSRpublic_cacheable:booleanconstructor(getter:ComputedGetter,privatereadonly_setter:ComputedSetter,isReadonly:boolean,isSSR:boolean){this.effect=newReactiveEffect(getter,()=>{if(!this._dirty){this._dirty=truetriggerRefValue(this)}})this.effect.computed=thisthis.effect.active=this._cacheable=!isSSRthis[ReactiveFlags.IS_READONLY]=isReadonly}getvalue(){//computed可能被其他proxies包使用,比如readonly(computed(()=>foo.bar)),所以得到这个const的原始对象self=toRaw(this)//收集依赖trackRefValue(self)//如果是脏数据或者SSR,需要重新计算if(self._dirty||!self._cacheable){//_dirty设置为false,防止重复计算self._dirty=false//计算self._value=self.effect.run()!}returnself._value}setvalue(newValue:T){this._setter(newValue)}}构造函数ComputedRefImpl构造函数接收四个参数:getter、setter、isReadonly(是否只读)、isSSR(是否为SSR)构造函数(getter:ComputedGetter,privatereadonly_setter:ComputedSetter,isReadonly:boolean,isSSR:boolean){this.effect=newReactiveEffect(getter,()=>{if(!this._dirty){this._dirty=truetriggerRefValue(this)}})//this.effect.computed指向thisthis.effect.computed=this//this.effect.activeandthis._cacheableinfalseinSSRthis.effect.active=this._cacheable=!isSSRthis[ReactiveFlags.IS_READONLY]=isReadonly}在构造函数中声明一个ReactiveEffect,在调度器中传入getter和一个调度函数作为参数如果_dirty为false,则设置_dirty为true并执行triggerRefValue函数.triggerRefValue可以接受两个值:ref、newVal。导出函数triggerRefValue(ref:RefBase,newVal?:any){ref=toRaw(ref)if(ref.dep){if(__DEV__){triggerEffects(ref.dep,{目标:ref,类型:TriggerOpTypes。SET,key:'value',newValue:newVal})}else{triggerEffects(ref.dep)}}}在triggerRefValue中,先获取ref的原始对象,如果ref的原始对象有dep属性,则触发依赖在部门。effect初始化后,这个effect会被赋值给ComputedRefImpl实例的effect属性,effect.computed会指向ComputedRefImpl实例的valuefunctiongetvalue(){//computed可能被其他proxy包裹,比如readonly(computed(()=>foo.bar)),所以要得到this的原始对象constself=toRaw(this)//收集依赖trackRefValue(self)//如果是脏数据,需要重新计算if(self._dirty||!self._cacheable){//_dirty为false,防止重复计算依赖关系self._dirty=false//计算self._value=self.effect.run()!}returnself._value}在读取ComputedRefImpl实例的value属性时,由于计算属性可能被其他代理包裹,需要使用toRaw获取其原始对象。constself=toRaw(this)然后调用trackRefValue来收集依赖。exportfunctiontrackRefValue(ref:RefBase){//如果允许收集并且存在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表示是否可以缓存(要看是否是服务端渲染,如果是server端渲染,它不能被缓存)。如果是脏数据或者不能缓存,那么_dirty会被设置为false,调用self.effect.run()修改self._value。if(self._dirty||!self._cacheable){self._dirty=falseself._value=self.effect.run()!}finallyreturnself._valuereturnself._valuevaluestoragefunctionsetvalue(newValue:T){this._setter(newValue)}当修改ComputedRefImpl实例的value属性时,会调用该实例的_setter函数。此时你会发现computed是惰性的,只有使用computed的返回结果才能触发相关计算。为了加深对computed的理解,我们通过一个例子来分析一下computed的缓存和计算过程:constvalue=reactive({foo:1})constcValue=computed(()=>value.foo)console.log(cValue.value)//1value.foo=2console.log(cValue.value)//2在打印cValue.value的时候,会命中ComputedRefImpl对应的get方法,在get中,执行trackRefValue收集对应的依赖(因为此时不是active状态的效果,即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),它是lazy的,如果没有使用computed属性,则不会计算,只有使用了,才会调用computed属性中的effect.run方法进行计算,结果会缓存在_value中。computed是如何重新计算的?首先,在第一次获取计算属性值的过程中,会进行依赖收集。假设computed属性的计算与响应对象的a、b属性相关,那么computed中生成的ReactiveEffect实例会被收集到targetMap[obj].a和targetMap[obj].b中,一次a或b属性改变,会触发依赖,在依赖触发过程中会执行调度函数,在调度函数为true时会设置脏数据标志_dirty,并触发计算属性的依赖.那么下次使用这个computed属性时,由于_dirty为true,就会调用computed属性中的effect.run方法重新计算值。