美文网首页
vue.js设计与实现(四)-响应系统-嵌套的effect与ef

vue.js设计与实现(四)-响应系统-嵌套的effect与ef

作者: 幸宇 | 来源:发表于2022-12-29 16:29 被阅读0次

    描述:
    在vue框架中我们会遇到组件的嵌套等等,那么嵌套的实现,当前程序代码是否可以实现,如下测试:

    const data = {foo:'foo',bar:'bar'}
        const bucket = new WeakMap()
        let activeEffect;
        // const effectStack = []
        function effect(fn){
            const effectFn = ()=>{
                cleanup(effectFn)
                activeEffect = effectFn;
                // effectStack.push(effectFn)
                fn()
                // 当副作用函数执行完毕后,将当前副作用函数弹出栈,并把activeEffect还原之前的值
                // effectStack.pop()
                // activeEffect = effectStack[effectStack.length-1]
            }
            effectFn.deps=[]
            effectFn()
        }
        function cleanup(effectFn){
            for(let i=0;i<effectFn.deps.length;i++){
                effectFn.deps[i].delete(effectFn)
            }
            effectFn.deps.length = 0
        }
        function track(target,key){
            if(!activeEffect) return
            let depsMap = bucket.get(target)
            if(!depsMap){bucket.set(target,depsMap=new Map())}
            let deps = depsMap.get(key)
            if(!deps){depsMap.set(key,deps=new Set())}
            deps.add(activeEffect)
            activeEffect.deps.push(deps)
        }
        function trigger(target,key){
            let depsMap = bucket.get(target)
            if(!depsMap) return
            const effects=depsMap.get(key)
            const effectsToRun = new Set(effects)
            effectsToRun.forEach(effect=>effect())
        }
        const obj = new Proxy(data,{
            get(target,key){
                track(target,key)
                return target[key]
            },
            set(target,key,newVal){
                target[key] = newVal
                trigger(target,key)
            }
        })
        let temp1,temp2;
        effect(
            function effectFn1(){
                console.log('effectFn1执行')
                effect(function effectFn2(){
                    console.log('effectFn2执行')
                    temp2 = obj.bar
                })
                temp1 = obj.foo
            }
        )
        setTimeout(()=>{
            obj.foo = 'foolll'
        })
    

    我们希望当修改 obj.foo 时会触发 effectFn1 执行。由于effectFn2 嵌套在 effectFn1 里,所以会间接触发 effectFn2 执行,而当修改obj.bar 时,只会触发 effectFn2 执行。但结果不是这样的,我们尝试修改obj.foo 的值,会发现输出为:


    image.png

    一共打印三次,前两次分别是副作用函数 effectFn1 与 effectFn2 初始执行的打印结果,到这一步是正常的,问题出在第三行打印。我们修改了字段 obj.foo 的值,发现 effectFn1 并没有重新执行,反而使得 effectFn2 重新执行了,这显然不符合预期。

    原因:
    我们用全局变量 activeEffect 来存储通过 effect 函数注册的副作用函数,这意味着同一时刻 activeEffect 所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且永远不会恢复到原来的值。这时如果再有响应式数据进行依赖收集,即使这个响应式数据是在外层副作用函数中读取的,它们收集到的副作用函数也都会是内层副作用函数,这就是问题所在。

    为了解决这个问题,我们需要一个副作用函数栈 effectStack,在副作用函数执行时,将当前副作用函数压入栈中,待副作用函数执行完毕后将其从栈中弹出,并始终让 activeEffect 指向栈顶的副作用函数。这样就能做到一个响应式数据只会收集直接读取其值的副作用函数,而不会出现互相影响的情况,如以下代码所示:

    const effectStack = []
        function effect(fn){
            const effectFn = ()=>{
                cleanup(effectFn)
                activeEffect = effectFn;
                effectStack.push(effectFn)
                fn()
                // 当副作用函数执行完毕后,将当前副作用函数弹出栈,并把activeEffect还原之前的值
                effectStack.pop()
                activeEffect = effectStack[effectStack.length-1]
            }
            effectFn.deps=[]
            effectFn()
        }
    

    打印结果:

    image.png

    如此一来,响应式数据就只会收集直接读取其值的副作用函数作为依赖,从而避免发生错乱。

    相关文章

      网友评论

          本文标题:vue.js设计与实现(四)-响应系统-嵌套的effect与ef

          本文链接:https://www.haomeiwen.com/subject/nuptcdtx.html