1.写在前面。上一篇介绍了effect的实现,可以用来注册副作用函数。同时允许一些选项参数,可以指定调度器。控制副作用函数执行的时机和次数等。还有跟踪和收集依赖关系的track函数,以及触发副作用函数重新执行的trigger函数。结合这些,我们可以实现一个计算属性--computed。2.延迟执行的效果在研究计算属性的实现之前,有必要了解一下延迟执行的效果(lazyeffect)。在目前设计的效果函数中,它在调用时立即执行传递的副作用函数。但实际上,我希望在某些场景下,我不希望它立即执行,而是只在需要时才执行。前面了解到,想要改变效果的执行方式,可以在options参数中设置。constdata={name:"pingping",age:18,flag:true}conststate=newProxy(data,{/*...*/})effect(()=>{console.log(state.name);},{//指定lazy选项,使函数不会立即执行lazy:true})这样,通过设置options选项,来修改效果函数的实现逻辑,当options.lazy为true,sideeffect不会立即执行function://effect用于注册sideeffect函数functioneffect(fn,options={}){consteffectFn=()=>{//调用函数完成cleanupleftoversideeffectfunctioncleanupEffect(effectFn)//调用effect时注册副作用函数,将副作用函数fn赋给activeEffectactiveEffect=effectFn;//在执行副作用函数之前压栈effectStack.push(effectFn)//执行副作用函数fn();//执行完成后出栈effectStack.pop()activeEffect=effectStack[effectStack.length-1]}//挂载options到effectFn函数effectFn.options=options//deps用于存放所有关联的依赖副作用函数effectFn.deps=[];//只有不懒惰时才执行if(!options.lazy){//执行副作用函数effectFneffectFn()}//否则返回副作用函数returneffectFn}在上面的代码片段中,首先判断effect函数中是否需要Lazy执行,当判断options.lazy的值为true时,会设置effectFn副作用函数作为参数返回生效。当用户调用执行效果函数时,可以通过返回值得到对应的effectFn函数,从而实现手动执行该函数。consteffectFn=effect(()=>{console.log(state.name);},{//指定lazy选项,使函数不会立即执行lazy:true});//手动执行副作用函数effectFn();但是仅仅实现副作用函数的手动执行,对于我们的使用意义不大。如果把返回effect的sideeffectfunction作为getter,那么可以通过这个valuefunction获取并返回任何值。consteffectFn=effect(()=>state.name+state.age,{//指定惰性选项,使函数不会立即执行lazy:true});//手动执行副作用函数,可以得到返回值constvalue=effectFn();这样就可以实现在调用的时候通过手动执行得到各种想要的值。只需要在effect函数内部做一些改动,执行sideeffect函数时只需要返回sideeffect的值即可://effect用于注册sideeffectfunctionfunctioneffect(fn,options={}){consteffectFn=()=>{//调用函数完成清理剩余的副作用functioncleanupEffect(effectFn)//当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffectactiveEffect=effectFn;//在执行副作用函数之前压栈effectStack.push(effectFn)//执行副作用函数并将执行结果存入resconstres=fn();//执行完成后出栈effectStack.pop()activeEffect=effectStack[effectStack.length-1]//使用res作为effectFn的返回值returnres}//挂载options到effectFn函数effectFn.options=options//deps用于存放所有与副作用函数相关的依赖effectFn.deps=[];//onlynon-lazyOnlyexecuteif(!options.lazy){//执行副作用函数effectFneffectFn()}//否则返回副作用函数returneffectFn}现在,我们已经实现了可以被惰性执行,可以得到执行返回Result,做后续处理。3.计算属性计算属性是惰性计算的。其实根据前面的设计和代码实现,计算属性函数大概有一个原型,就是接收一个getter函数作为sideeffectfunction,创建一个lazilyexecutiveeffect。计算函数的执行会返回一个包含访问器属性的对象,只有当读取到值时才会执行effectFn并返回结果。functioncomputed(getter){consteffectFn=effect(getter,{//指定惰性选项,使函数不会立即执行lazy:true});conststate={//当读取到值时,执行effectFn并返回结果getvalue(){returneffectFn();}}returnstate;}上面的代码中只是粗略地进行了惰性计算,只有在真正读取到sumRes.value的值时才进行。进行计算并获取值。但是多次读取sumRes.value的值后,每次访问计算出来的值都是一样的,不符合我们需要使用上一次计算出来的值的要求。"计算属性需要一个缓存机制,以便可以使用上次计算的结果。』constsumRes=computed(()=>state.name+state.age);console.log("hello",sumRes.value);console.log("hello",sumRes.value);console.log("hello",sumRes.value);运行结果:出现这种情况的原因是多次读取sumRes.value的值时,effectFn每次访问都会被再次调用重新计算computedwithcache为了解决上次计算的值无法获取的问题,在实现computed函数的时候需要为计算值增加一个缓存操作,其实,实现很简单,就是加两个变量value和dirty,value用来缓存上次计算的值,dirty表示是否需要重新计算。计算函数(getter){让值;让脏=真;consteffectFn=effect(getter,{//指定lazy选项,使函数不会立即执行lazy:true,//在调度器中将dirty重置为truescheduler(){dirty=true}});conststate={//读取值时,执行effectFn并返回结果价值如果(脏){价值=effectFn();dirty=false}returnvalue}}returnstate;}上面代码中,初始设置dirty为true,这样计算出来的值就被缓存起来,下次进行相同的computed计算操作时,会使用缓存的值直接而不是每次都重新计算。同时给计算函数的effect添加scheduler属性,在函数内部将dirty的值重置为true,下次访问sumRes.value时再次调用effectFn的计算值。constsumRes=computed(()=>state.name+state.age);console.log("你好",sumRes.value);console.log("你好",sumRes.value);console.log("你好",sumRes.value);状态年龄++;console.log("你好",sumRes.value);执行结果为:但是,在另一个效果函数中读取当前设计的计算属性时,修改响应数据状态上的属性值,并不会触发副效果函数的重新渲染。其实根本原因是这里存在一个effect嵌套问题。computed内部由effect函数实现,读取effect中computed的值相当于嵌套了effect。外部效果不会被内部效果响应。数据采集??。当然,问题很简单,解决方法也同样简单。只需要在读取计算属性的值时手动调用track函数进行跟踪即可。当计算属性依赖的响应数据发生变化时,手动调用触发函数触发响应:functioncomputed(getter){letvalue;让脏=真;consteffectFn=effect(getter,{//指定惰性选项,使函数不会立即执行lazy:true,//在调度器中将dirty重置为truescheduler(){dirty=truetrigger(state,"value")}});conststate={//读取值时,执行effectFn并返回结果(脏){值=effectFn();dirty=false}//对value进行值操作时,手动调用track函数跟踪track(state,"value")returnvalue}}returnstate;}写一个简单的demo进行实验:constsumRes=computed(()=>state.name+state.age);console.log("你好",sumRes.value);console.log("hello",sumRes.value);console.log("hello",sumRes.value);effect(()=>{console.log(sumRes.value);})state.age++console.log("你好",sumRes.value);执行结果:根据上面的实现demo,可以分析对应计算属性的响应关系图:计算属性的响应关系4.最后写上计算属性computed实际上是一个延迟执行的副作用函数。您可以使用lazy选项来延迟执行副作用函数。标记为惰性执行的副作用函数可以手动执行。在读取计算属性的值时,可以手动执行副作用函数。当响应式数据发生变化时,通过调度器将dirtyflag设置为true,即脏数据。下次读取计算属性的值时,它会重新计算以获得真正的值。
