分支切换constdata={ok:true,text:'helloworld'}constobj=newProxy(data,{/*....*/}effect(functioneffectFn(){document.body.innerText=obj.ok?obj.text:'not'})如上代码,effectFn函数中有一个三元表达式,根据obj的ok属性的值执行对应的代码,当ok属性发生变化时,执行的代码也会发生变化,也就是所谓的分支Switch,在effectFn中,如果ok的值为true,就会触发ok和text的读取操作,所以这个函数已经和这两个response数据建立了关系,如下图:但是当ok为false时,不会text,所以不管text的值怎么变化,DOM都不会更新,但是因为绑定了辅助函数,所以函数还是会运行,这不应该。为了解决这个问题,你可以在每次执行副作用函数时,将它从所有关联的集合中删除。T因此,需要记录哪些集合(即上图中的Set)包含副作用函数,并为副作用函数添加一个属性,值为数组,用于记录,如下代码://使用全局变量存储注册的副作用函数letactiveEffect=undefinedfunctioneffect(fn){consteffectFn=()=>{//effectFn执行时,设置为当前激活的副作用函数activeEffect=effectFnfn()}//activeEffect.deps用于存放所有包含副作用函数的集合(即与副作用函数关联)SetactiveEffect.deps=[]//执行副作用函数effectFn()}副作用函数为在Proxy中拦截读操作时绑定,所以在拦截读操作中可以收集到包含副作用函数的集合:turn//根据target从WeakMap中获取MapletdepsMap=bucket.get(target)if(!depsMap)bucket.set(target,(depsMap=newMap()))letdesp=depsMap.get(key)if(!deps)depsMap.set(key,(deps=newSet()))//将当前激活的副作用函数添加到依赖集合中deps.add(activeEffect)//deps是当前副作用函数的集合,即Linkeddependencycollection//AddittoactiveEffect.depsactiveEffect.deps.push(deps)}关系如下图:清除依赖根据上图关系,每次执行sideeffect函数,根据effectFn.deps从依赖中删除副作用函数(Set)//使用全局变量存储注册的副作用函数letactiveEffect=undefinedfunctioneffect(fn){consteffectFn=()=>{//调用cleanup函数完成清理cleanup(effectFn)//effectFn执行时,设置为当前激活的sideeffectfunctionactiveEffect=effectFnfn()}//activeEffect.deps用于存放所有包含副作用函数的集合(即与副作用函数相关联)setactiveEffect.deps=[]//执行副作用函数effectFn()}functioncleanup(effectFn){//遍历数组for(letitemofeffectFn.deps){//item是副作用函数的集合(Set)item.delete(effectFn)}//最后重置数组effectFn.deps.length=0}但这会导致当前响应代码死循环。问题出在拦截设置操作上:fn())//问题出在这一行}上面代码中,最后一行遍历的effects其实就是当前key的sideeffects的集合。在遍历过程中,副作用函数会运行。这时候会通过cleanup进行清理,但是执行sideeffectfunction会重新收集到同一个set中。该函数会导致无限循环。可以使用另一个SSet遍历functiontrigger(target,key){constdepsMap=bucket.get(target)if(!depsMap)returnconsteffects=depsMap.get(key)consteffectsToRun=newSet(effects)effectsToRun.forEach(fn=>fn())}Responsivecompletecodesofar//Bucketforstoringsideeffectfunctionsconstbucket=newWeakMap()//FunctionforstoringregisteredsideeffectsletactiveEffect=undefinedfunctioncleanup(effectFn){for(letitmeofeffectFn.deps){itme.delete(effectFn)}effectFn.deps.length=[]}函数effect(fn){consteffectFn=()=>{cleanup(effectFn)activeEffectt=effectFnfn()}effectFn.deps=[]effectFn()}constdata={text:'helloworld',ok:true}constobj=newProxy(data,{//拦截读取操作get(target,key){track(target,key)//返回属性值returntarget[key]},//拦截设置操作set(target,key,newVal){//设置属性值target[key]=newValtrigger(target,key)}})functiontrack(target,key){//没有activeEffect,直接返回if(!activeEffect)returntarget[key]//根据target返回'bucket'中的depsMap,也是一个Map类型:key--->effectsletdepsMap=bucket.get(target)//如果depsMap不存在,创建一个新的Map并关联targetif(!depsMap)bucket.set(target,(depsMap=newMap()))//然后根据key从depsMap中获取deps,它是一个Set类型//里面存放了当前key相关的所有副作用函数:effectsletdeps=depsMap.get(key)//如果deps没有存在,创建一个新的Set并与键0关联,如果!deps)depsMap.set(key,(deps=newSet()))//最后将当前激活的副作用函数添加到“桶”中deps.add(activeEffect)}functiontrigger(target,key){//根据target从'bucket'中获取depsMap,它是key-->effectsconstdepsMap=bucket.get(target)if(!depsMap)return//根据key获取所有副作用函数effectsconsteffects=depsMap.get(key)//执行副作用函数//effects&&effects.forEach(fn=>fn())consteffectsFnRun=newSet(effects)effectsFnRun.forEach(fn=>fn())}effect(()=>{console.log('effectrun');document.body.innerText=obj.ok?obj.text:'not'})setTimeout(()=>{obj.ok=假},2000)
