当前位置: 首页 > Web前端 > HTML

Vue3源码分析(computed-计算属性)

时间:2023-04-02 20:10:55 HTML

作者:秦智英前言上一篇我们分析了Vue3响应式的全过程。在本文中,我们将分析Vue3中的计算属性是如何实现的。在Vue2中,我们已经对计算属性有了清晰的认识。在Vue3中,计算函数作为计算属性的API提供。下面我们从源码的角度来分析一下计算属性的运行过程。computedexport函数computed(getter:ComputedGetter):ComputedRefexportfunctioncomputed(options:WritableComputedOptions):WritableComputedRefexportfunctioncomputed(getterOrOptions:ComputedGetter|WritableComputedOptions){getter:ComputedGetterletsetter:ComputedSetterif(isFunction(getterOrOptions)){getter=getterOrOptionssetter=NOOP}else{getter=getterOrOptions.getsetter=getterOrOptions.set}returnnewComputedRefImpl(getter,setter,isFunction(getterOrOptions)||!getterOrOptions.set)asany}开头函数重载的方法允许计算函数接受两种类型的参数:第一种是getter函数,第一种是第二个是具有get和set的对象。接下来就是根据传入的参数类型不同,初始化函数内部的getter和setter函数。如果传入一个函数类型的参数,那么getter就是这个函数,setter就是一个空操作。参数是一个对象,那么getter就等于这个对象的get函数,setter就等于这个对象的set函数。在函数的最后,返回了一个新的ComputedRefImpl,并将我们之前标准化的参数传递给了这个构造函数。让我们分析一下ComputedRefImpl构造函数。ComputedRefImplclassComputedRefImpl{//缓存结果private_value!:T//重新计算开关private_dirty=truepublicreadonlyeffect:ReactiveEffectpublicreadonly__v_isRef=true;publicreadonly[ReactiveFlags.IS_READONLY]:booleanconstructor(:ComputedGetter,privatereadonly_setter:ComputedSetter,isReadonly:boolean){//包装传入的getter函数this.effect=effect(getter,{lazy:true,//安排执行scheduler:()=>{if(!this._dirty){this._dirty=true//调度通知trigger(toRaw(this),TriggerOpTypes.SET,'value')}}})}//访问计算属性时默认调用此时的get函数getvalue(){//是否需要重新计算if(this._dirty){this._value=this.effect()this._dirty=false}//访问时进行依赖收集访问这个计算属性是一个副作用函数track(toRaw(this),TrackOpTypes.GET,'value')returnthis._value}setvalue(newValue:T){this._setter(newValue)}}ComputedRefImpl类内部维护了两个非常重要的私有属性,_value和_dirty,其中_value用来缓存我们计算的结果,_dirty用来控制是否需要重复计算。我们来看看这个函数的内部工作原理。首先构造函数在初始化的时候使用effect函数来包装传入的getter(我们在上一篇文章中分析了effect函数的作用,将传入函数变成了响应式的sideeffect函数),但是这里我们传入了一些配置效果的参数。还记得我们分析触发函数时的这段代码:construn=(effect:ReactiveEffect)=>{if(effect.options.scheduler){effect.options.scheduler(effect)}else{effect()}}effects.forEach(run)当某个属性的值发生变化时,会触发trigger函数调度更新,循环遍历所有依赖该属性的effect函数,并使用run函数执行effect,如果effect参数中配置了scheduler,则执行scheduler函数而不是依赖的sideeffect函数。当computed属性依赖的属性发生变化时,会执行包裹getter函数的effect,但是因为配置了scheduler函数,所以真正执行的是scheduler函数,并没有执行computed属性的getter函数scheduler函数获取新值,但将_dirty设置为false,然后通知依赖计算属性的sideeffect函数进行更新。当依赖计算属性的副作用函数收到通知后,会访问计算属性的get函数,然后根据_dirty的值判断是否需要重新计算。回到我们的构造函数,我们只需要记住我们在构造函数中初始化了三个重要的点:第一:使用effect函数来包装传入的getter函数。第二:在使用效果包装的过程中,我们会执行getter函数。此时在getter函数的执行过程中,会将当前计算出的属性收集到所访问属性对应的依赖集合中。第三:传入配置参数lazy和scheduler,这些配置参数用于控制当前计算属性订阅的属性发生变化时,对计算属性的调度时机。然后我们继续分析get值。当我们访问计算属性的值时,我们实际上访问了这个函数的返回值。它会根据_dirty的值判断getter函数是否需要重新计算。如果_dirty为真,则需要重新执行效果函数,并将effect的值设置为false,否则返回之前缓存的_value值。在访问计算出的属性值阶段,调用track函数收集依赖关系。此时收集访问计算属性值的副作用函数,key始终为vlaue。最后,在设置计算属性的值时,会执行set函数,然后调用我们传入的_setter函数。示例流程至此,计算属性的执行流程分析完毕。下面结合一个例子来了解整个过程:下面是流程图。当点击页面上的按钮更改testData的值时,更改过程如下图红线所示。首先,在初始化页面时,testData经过ref()后会变成响应式数据,它会收集依赖访问testData.value的值。当testData.value的值发生变化时,会分发依赖于这个值的依赖集合。一个getter函数被传入到updatecomputed中,并且在getter函数内部有一个对testData.value的访问。此时当前computed属性的sideeffect函数订阅了testData.value的值,computed返回一个值,而该组件获取到computed的返回值,页面的renderingsideeffect函数订阅到计算的返回值,因此此页面中有两个依赖集。当我们点击页面上的按钮时,testData.value的值就会改变。此时会通知订阅计算属性的副作用函数进行更新操作。由于我们在生成计算属性的副作用时配置了调度程序,因此执行了调度程序函数。scheduler函数不会立即执行getter函数重新计算,而是将ComputedRefImpl类内部的私有变量_dirty设置为true,然后通知订阅当前计算属性的副作用函数执行更新操作。组件中的渲染副作用函数在执行update操作时会访问getvalue函数,该函数会根据_dirty值判断是否需要重新计算。由于之前的scheduler函数设置了_dirty为true,此时会调用getter函数sideeffect函数effect此时会重新计算并返回结果,同时更新页面数据。总结一下,计算属性最大的两个特点是,当计算属性依赖延迟计算的值发生变化时,不会立即执行getter函数重新计算新的结果,而是会打开重新计算开关,订阅计算属性的副作用将被通知。函数来更新。如果当前计算属性没有依赖集,则不会执行重新计算逻辑。如果存在触发获取计算属性的依赖,则将调用this.effect()进行重新计算。缓存结果当依赖属性没有改变时,访问计算属性将返回之前缓存在_value中的值。对电子感兴趣?请关注我们的开源项目ElectronPlayground,带你快速上手Electron。每周五我们都会挑选一些有趣的文章和新闻与大家分享。快来关注我们的小风周刊吧。我们是好未来小黑板的前端技术团队。我们会经常与您分享最新最酷的行业技术知识。欢迎知乎、掘金、Segmentfault、CSDN、简书、开源中国、博客园关注我们。