Vue3响应式系统的原理——03.建立副作用函数和目标字段之间的对应关系

94 阅读2分钟

首先回顾下02中存在的问题

  • 1.无论读取哪个属性都会将副作用函数activeEffect加入集合中。
        get(target,key){
            if(activeEffect){
                //无论读取的是哪个属性,都会将副作用函数加入桶中
                bucket.add(activeEffect);
            }     
            return target[key];
        },
  • 2.只要触发set都会执行副作用函数。
    setTimeout(() => {
        //同样会触发响应式,因为触发了代理对象的setter
        obj.noExsit = "响应式的改变"
    },2000)

1.为了解决以上问题,需要建立两种映射关系

  • 1.对象 -> key之间的映射关系
  • 1.key -> 副作用函数之间的映射关系

2.使用WeakMap建立target和之间的关系

    const bucket = new WeakMap();

    let depsMap = bucket.get(target);
    if(!depsMap){
        bucket.set(target,(depsMap = new Map()));
    }

2.使用Map建立字段key和副作用函数之间的关系

            let deps = depsMap.get(key);
            if(!deps){
                //Set存放副作用函数
                depsMap.set(key,(deps = new Set()))
            }
            deps.add(activeEffect);

3.完整代码

const data = {text:"hello world"};
    //定义存放副作用函数的WeakMap
    //WeakMap存放对象 -> Map之间的映射关系
    const bucket = new WeakMap();
    //对原始数据进行代理
    const obj = new Proxy(data,{
        //读取值时,将副作用函数加入桶中
        get(target,key){
            if(!activeEffect){
               return target[key];
            }     
            let depsMap = bucket.get(target);
            if(!depsMap){
                bucket.set(target,(depsMap = new Map()));
            }
            // Map存放key与副作用函数之间的映射关系
            let deps = depsMap.get(key);
            if(!deps){
                //Set存放副作用函数
                depsMap.set(key,(deps = new Set()))
            }
            deps.add(activeEffect);
            return target[key];
        },
        //改变值时进行操作的拦截
        set(target,key,newVal){
            target[key] = newVal;
            const depsMap = bucket.get(target);
            if(!depsMap) return;
            const effects = depsMap.get(key);
            //取到该target下key对应的副作用函数Set
            effects && effects.forEach(fn =>fn());
        }
    })
    let activeEffect;
    //通过effect函数来注册响应式函数
    function effect(fn){
        activeEffect = fn;
        fn()
    }
    
    effect(() => {
        console.log("effect run");
        document.body.innerText = obj.text;
    })

    setTimeout(() => {
        obj.text = "响应式的改变"
    },2000)

    setTimeout(() => {
        obj.noExsit = "响应式的改变"
    },2000)

4.总结

  • 以下代码不会触发页面变化
    setTimeout(() => {
        //不会触发响应式,因为const depsMap = bucket.get(target);找不到对应的关系
        obj.noExsit = "响应式的改变"
    },2000)