本文转载自微信公众号《前端引力》,作者一川。转载本文请联系前端Gravity公众号。前面写到Computedproperty是Vue开发中一个非常实用的API,可以让用户自定义一个计算方法,然后根据一些依赖的响应式数据计算出一个新的值并返回。当依赖发生变化时,计算属性会自动重新计算得到新值,使用方便。我们看到计算属性本质上是一个依赖的计算,为什么不直接使用函数呢?Vue3中计算属性是如何实现的?下面简单看一下计算属性的例子,看到之后设置计算属性addOne后,直接改变addOne.value的值会报错,但是只改变原值count.value不会导致错误。这是因为:如果一个函数传递给computed,它是一个getter函数,只能获取它的值,不能直接修改它。在getter函数中,根据响应对象重新计算一个新值,称为计算属性。这个响应式对象调用了计算属性的依赖constcount=ref(1);constaddOne=computed(()=>count.value+1);console.log(addOne.value);//2addOne.value++;//错误计数。value++;console.log(count.value);//3那么,我们应该如何修改addOne.value的值呢?即在computed中设置set函数来自定义值。constcount=ref(1);constaddOne=computed({get:()=>count.value+1,set:val=>count.value=val-1});addOne.value=1;console.log(count.value);//0我们研究源码:exportfunctioncomputed(getterOrOptions:ComputedGetter|WritableComputedOptions){letgetter:ComputedGetterletsetter:ComputedSetter//如果输入是一个function,说明是Read-onlycomputedif(isFunction(getterOrOptions)){getter=getterOrOptionssetter=__DEV__?()=>{console.warn('Writeoperationfailed:computedvalueisreadonly')}:NOOP}else{//不是方法说明是自定义gettersettergetter=getterOrOptions。getsetter=getterOrOptions.set}letdirty=trueletvalue:Tletcomputed:ComputedRef//创建一个effect,我们看effect的源码就知道,传入的lazy表示不会立即执行,computed表示当computed的上游依赖发生变化时,会优先调用triggerrunnereffect,scheduler会调用scheduler而不是直接调用effectconstrunner=effect(getter,{lazy:true,//markeffectascomputedsothatitgetspriorityduringtriggercomputed:true,scheduler:()=>{//触发更新时设置dirty为true,不会立即更新if(!dirty){dirty=truetrigger(computed,TriggerOpTypes.SET,'value')}}})//构造一个computedreturncomputed={__v_isRef:true,//exposeeffectsocomputedcanbestoppedeffect:runner,getvalue(){//当dirty为true时,get操作,执行effect获取最新的值//if(dirty){value=runner()dirty=false}//dirty为false,说明该值没有更新,直接返回track(computed,TrackOpTypes.GET,'value')returnvalue},setvalue(newValue:T){setter(newValue)}}asanyreturncomputed}computed计算属性有两个特点:延迟计算:只有当我们访问计算属性时,计算getter函数才真正运行计算缓存:它会缓存内部最后计算的值,只有当dirty为真时,才会重新计算。如果访问计算属性时dirty为false,则直接返回值。那么,计算属性的好处就是:只要依赖不发生变化,就可以使用缓存的值,而不用每次都重新设置。当一个组件被渲染时,函数被执行来计算一个嵌套计算的小例子。我们可以看到对于addOne,它收集的依赖是组件副作用渲染函数,而对于count,它收集的依赖是在addTwo里面的runner函数。当我们修改计数值时,将发送通知。首先运行addOne中的setter函数,此时addOne中的dirty值会变为true,然后trigger函数再次dispatch通知;然后运行addTwo中的setter函数,然后将脏值设置为true;当我们再次访问addTwo中的值,发现dirty值为true时,会执行addTwo的computed函数,先执行addOne.value+1,再执行addOne的computed函数。数数。value+1,这样最后打印出来的值为2。constcount=ref(0);constaddOne=computed(()=>{returncount.value+1;//1})constaddTwo=computed(()=>{returnaddOne.value+1;//2})console.log(addTwo.value);//2得益于计算属性的巧妙设计,无论嵌套多少层都能正常运行。从“vue”导入{ref,计算};从“@vue/reactivity”导入{effect};constcount=ref(0);constaddOne=computed(()=>{returncount.value+1;})effect(()=>console.log(addOne.value+count.value))functionadd(){count.value++;}add();我们看到上面代码最后的输出是:133当我们第一次执行addOne计算属性时,count.value的值还是0,addOne.value的值是1,会触发并执行效果,此时打印的值还是1。然后执行add()函数,会修改count.value的值,触发并执行effect函数,因为addOne也是effect的依赖,addOne的runners函数也是count.value的依赖,并且count.value的修改会执行runners函数,addOne的依赖会再次执行,然后会触发effect函数,所以会输出两次3。computed函数返回的对象实际上劫持了value属性的getter和setter,但是为什么我们在组件模板中访问一个computed属性变量,而不是手动添加.value呢?参考文章《Vue中文社区》《Vue中文社区》《Vue3中文文档》写在最后理解计算属性的工作机制,理解计算属性嵌套场景代码的执行顺序,知道计算属性的两个特点——延迟计算和缓存,在组件开发中合理使用计算属性。