vue.js设计与实现-响应系统-computed和lazy

2023-01-09  本文已影响0人  幸宇

我们给effect函数加入lazy属性,来实现懒执行,在有些场景下,我们并不希望它立即执行,而是希望在需要的时候才执行,例如计算属性,我们在options里面添加lazy属性来达到目的,即如下:

effect(
    ()=>{console.log(obj.foo)},
    {
        lazy:true
    }
)

lazy选项和之前介绍的scheduler一样,它通过options选项对象指定,有了它,我们就可以修改effect函数实现逻辑,当options.lazy为true时,则不立即执行副作用函数:

 function effect(fn,options={}){
        const effectFn=()=>{
            cleanup(effectFn)
            activeEffect = effectFn
            effectStack.push(effectFn)
            const res = fn()
            effectStack.pop()
            activeEffect = effectStack[effectStack.length-1]
            return res
        }
        effectFn.deps=[]
        effectFn.options = options
        if(!options.lazy){
            effectFn()
        }
        return effectFn
    }

我们想实现通过定义computed函数来实现计算,实现方法如下:

function computed(getter){
    let value
    // dirty用来标识是否重新计算值
    let dirty = true
    const effectFn=effect(
        getter,
        {
            lazy:true,
            // 属性值发生变化需要dirty值变为true,重新计算
            scheduler(){
                dirty = true
            }
        }
    )
    const obj = {
        get value(){
            if(dirty){
                value = effectFn()
                // 将dirty设置为false,下一次访问直接使用缓存到value中的值
                dirty = false
            }
            return value
        }
    }
    return obj
}

使用时:

const sumRes = computed(()=>obj.bar+obj.foo)
console.log(sumRes.value)

但假如我们在外面这样使用:

const sumRes = computed(()=>obj.bar+obj.foo)
effect(()=>{
    console.log(sumRes.value)
})
obj.foo++
console.log(sumRes.value)

那么effect函数里还是打印2

原因:
从本质上看这就是一个典型的 effect 嵌套。一个计算属性内部拥有自己的 effect,并且它是懒执行的,只有当真正读取计算属性的值时才会执行。对于计算属性的 getter 函数来说,它里面访问的响应式数据只会把computed 内部的 effect 收集为依赖。而当把计算属性用于另外一个 effect 时,就会发生 effect 嵌套,外层的 effect 不会被内层 effect 中的响应式数据收集。

解决办法:

我们可以手动调用 track 函数进行追踪;当计算属性依赖的响应式数据发生变化时,我们可以手动调用trigger 函数触发响应:

function computed(getter){
        let value
        let dirty = true
        const effectFn=effect(
            getter,
            {
                lazy:true,
                scheduler(){
                    if(!dirty){
                        dirty = true
                        // 当计算属性依赖响应式数据变化时,手动调用trigger函数
                        trigger(obj,'value')
                    }
                }
            }
        )
        const obj = {
            get value(){
                if(dirty){
                    value = effectFn()
                    dirty = false
                }
                // 当读取value时,手动调用track函数追踪
                track(obj,'value')
                return value
            }
        }
        return obj
    }

最后整体代码如下:可自行测试

const data={foo:1,bar:1}
    let activeEffect;
    const bucket = new WeakMap()
    const effectStack = []
    function effect(fn,options={}){
        const effectFn=()=>{
            cleanup(effectFn)
            activeEffect = effectFn
            effectStack.push(effectFn)
            const res = fn()
            effectStack.pop()
            activeEffect = effectStack[effectStack.length-1]
            return res
        }
        effectFn.deps=[]
        effectFn.options = options
        if(!options.lazy){
            effectFn()
        }
        return 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 && effects.forEach(effectFn=>{
            if(effectFn!==activeEffect){
                effectsToRun.add(effectFn)
            }
        })
        // 调度执行
        effectsToRun.forEach(effectFn=>{
            if(effectFn.options.scheduler){
                effectFn.options.scheduler(effectFn)
            }else{
                effectFn()
            }
        })
    }
    const obj = new Proxy(data,{
        get(target,key){
            track(target,key)
            return target[key]
        },
        set(target,key,newVal){
            target[key] = newVal
            trigger(target,key)
        }
    })
    
    function computed(getter){
        let value
        let dirty = true
        const effectFn=effect(
            getter,
            {
                lazy:true,
                scheduler(){
                    if(!dirty){
                        dirty = true
                        // 当计算属性依赖响应式数据变化时,手动调用trigger函数
                        trigger(obj,'value')
                    }
                }
            }
        )
        const obj = {
            get value(){
                if(dirty){
                    value = effectFn()
                    dirty = false
                }
                // 当读取value时,手动调用track函数追踪
                track(obj,'value')
                return value
            }
        }
        return obj
    }
    const sumRes = computed(()=>obj.bar+obj.foo)
    effect(
        ()=>{
            console.log(sumRes.value)
        }
    )
    obj.foo++
    console.log(sumRes.value)
上一篇 下一篇

猜你喜欢

热点阅读