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

2022-12-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

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

上一篇下一篇

猜你喜欢

热点阅读