写在前面。计算属性是Vue开发中一个非常实用的API。它允许用户自定义一个计算方法,然后根据一些依赖的响应数据计算出新的值。价值和回报。当依赖发生变化时,计算属性会自动重新计算得到新值,使用方便。我们看到计算属性本质上是依赖的计算,为什么不直接使用函数呢?Vue3中的计算属性是如何实现的?计算属性computed我们先举个简单的例子。我们看到设置计算属性addOne后,如果前端训练直接改变addOne.value的值,会报错。只有改变原值count.value才不会报错。这是因为:如果一个函数传递给computed,它是一个getter函数,只能获取它的值,不能直接修改它。在getter函数中,根据响应对象重新计算一个新值,称为计算属性。这个响应式对象调用了计算属性的依赖constcount=ref(1);constaddOne=computed(()=>count.value+1);console.log(addOne.value);//2addOne.value++;//errorcount.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//IfTheinputisfunctionanddescriptionisread-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表示当effecttrigger被调用时,会调用scheduler而不是直接调用effectconstrunner=effect(getter,{lazy:true,//markeffectascomputed这样它在triggercompute期间获得优先权d:true,scheduler:()=>{//触发更新时设置dirty为true,如果(!dirty){dirty=truetrigger(computed,TriggerOpTypes.SET,'value')}}则不会立即更新})//构造一个computedreturncomputed={__v_isRef:true,//暴露effect所以computed可以停止effect:runner,getvalue(){//dirty为true,get操作时,执行effect获取最新值//if(dirty){value=runner()dirty=false}//dirty为false,表示value没有更新,直接returntrack(computed,TrackOpTypes.GET,'value')returnvalue},setvalue(newValue:T){setter(newValue)}}asanyreturncomputed}Computed计算属性有两个特点:延迟计算:只有当我们访问计算属性时,computedgetter函数才会真正运行计算缓存:它会缓存最后一个内部计算结果值,只有dirty为true时才会重新计算。如果访问计算属性时dirty为false,则直接返回值。那么,计算属性的好处就是:只要依赖不发生变化,就可以使用缓存的值,而不需要每次都重新渲染组件作为嵌套计算的一个小例子,我们可以看到对于addOne,依赖web前端训练收集的是组件副作用渲染函数,而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这样最后打印出来的值为2constcount=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))函数add(){count.value++;}add();我们看到上面代码最后的输出是:133当我们第一次执行addOne的计算属性时,count.value的值还是0,而addOne.value的值是1,effect会被触发执行,此时打印的值还是1。然后执行add()函数,会修改count.value的值,触发并执行effect函数,因为addOne也是effect的依赖,addOne的runners函数也是count.value的依赖,并且count.value的修改会执行runners函数,addOne的依赖会再次执行,然后会触发effect函数,所以会输出两次3。computed函数返回的对象实际上劫持了value属性的getter和setter,但是为什么我们在组件模板中访问一个computed属性变量,而不是手动添加.value呢?