vue3 设计一个完善的响应式系统

81 阅读3分钟
        /* 
         bucket
         桶:用来存储副作用函数
         副作用函数概念:可以简单理解为函数
        */
        const bucket = new Set()
          /* 
         data
         原始对象
        */
        let data = {
            text: '测试文案1'
        }

        /* 
         注册副作用函数
         可以理解为给函数指定名字,为了方便存储和查找
        */
        let activeEffect;
        function effect (fn) {
            activeEffect = fn 
            fn()
        }

        /* 
         Proxy 的get 和 set 方法实现数据响应式
        
        */
        const obj = new Proxy(data,{
            get(target, key) {
                // console.log('target---', target)
                console.log('key---', key)
                if(activeEffect) {
                    bucket.add(activeEffect)
                }
                return target[key]
            },
            set(target, key, newVal) {
                console.log('newVal---', newVal);
                target[key] = newVal
                bucket.forEach(fn=>fn())
                return true
            }
        })

        /* 
         匿名副作用函数调用
        */
        effect(
            () => {
                console.log('effect run')
                document.body.innerText = obj.text 
            }
        )

        /* 
          以上桶的存储方式的缺点:
          给obj添加新属性,也会触发匿名副作用函数  console.log('effect run') 会执行两次
        */
        setTimeout(() => {
           obj.notExist = 'hello';
        }, 1000);
  // -------------------------------------------------以下为完善的响应系统
     /* 
       问题:通过上面响应系统的设计可以发现只要操作原始对象就会触发相应桶内的副作用函数
       解决:如何在需要触发的时机触发,则需要重新设计 “桶” 的数据结构

       桶数据结构的设计需要了解 WeakMap 、Map、Set 之间的关系

       以下为个人理解的总结:(可以先看part two 部分的代码,再来理解)

       WeakMap 的 key 为 原始对象obj
       Map 的 key 为 obj的key
       Set 直接存储副作用函数


 存储对象   原始对象  对象的key    副作用函数       
            obj ---  key1       --- effect1
                                --- effect2
                ---  key2       --- effect3
                                --- effect4
                
                |         |             |                  
                |         |             |
 存储方式      WeakMap    Map           Set            


     */



     /* 
        bucketPro
        桶:用来存储副作用函数
        WeakMap 的 key必须是对象
     */
     const bucketPro = new WeakMap()

     /* 
        data
        原始对象
     */
     let dataPro = {
        text: '测试文案1'
     }

     /* 
        注册副作用函数
        可以理解为给函数指定名字,为了方便存储和查找
    */
    let activeEffectPro;
    function effectPro (fn) {
        activeEffectPro = fn 
        fn()
    }

    
     /* 
      修改get/set 拦截器
     */
     const objPro = new Proxy(dataPro,{
            // 拦截读取操作
            get(target, key) {
                if(!activeEffectPro) return; // 没有activeEffectPro直接return
                // 根据target 从 桶中获取depsMap, 是一个Map 类型 key ---> efftcts
                let depsMap = bucketPro.get(target)
                // 如果不存在,则新建一个Map 并与target 关联
                if(!depsMap) {
                    bucketPro.set(target, (depsMap = new Map())) 
                }
                
                // 再根据key 从 depsMap中获取deps , 是一个Set 类型
                // 里面存储这所有与key 相关联的副作用函数 effects
                let deps = depsMap.get(key)
                // 如果不存在,则新建一个Set 并与key 关联
                if(!deps) {
                    depsMap.set(key, (deps = new Set())) 
                }

                // 将副作用函数添加到deps中
                deps.add(activeEffectPro)

                // 返回属性值
                return target[key]
            },

            // 拦截设置操作
            set(target, key, newVal) {
                // 设置属性值
                target[key] = newVal
                // 根据target 从桶中取得depsMap , 它是 key ---> effects
                const depsMap = bucketPro.get(target)
                if(!depsMap) return 
                // 根据key 取得所有副作用函数 effects
                const effects = depsMap.get(key)
                // 执行副作用函数
                effects && effects.forEach(fn=>fn())
            }
        })


      /* 
         匿名副作用函数调用
      */
      effectPro(
        () => {
            console.log('effectPro run')
            document.body.innerText = objPro.text 
        }
      )

    /* 
    给objPro添加新属性,发现并不会触发effectPro 函数
    完美解决!!!!
    */
    setTimeout(() => {
        objPro.notExist = 'hello';
      }, 1000);


    // --------------------------------------------------------------------------------------end



    /*
    
    进阶: get拦截函数中“桶”的数据结构设置 可单独封装到track函数中,同样我们可以把触发副作用函数重新执行的逻辑封装到trigger函数中
    
    */

    const objPro = new Proxy(dataPro,{
            // 拦截读取操作
            get(target, key) {
               
                track(target, key)
                // 返回属性值
                return target[key]
            },

            // 拦截设置操作
            set(target, key, newVal) {
                // 设置属性值
                target[key] = newVal
                trigger()
            }
        })


    function track(target, key) {
        if(!activeEffectPro) return; // 没有activeEffectPro直接return
        // 根据target 从 桶中获取depsMap, 是一个Map 类型 key ---> efftcts
        let depsMap = bucketPro.get(target)
        // 如果不存在,则新建一个Map 并与target 关联
        if(!depsMap) {
            bucketPro.set(target, (depsMap = new Map())) 
        }
        
        // 再根据key 从 depsMap中获取deps , 是一个Set 类型
        // 里面存储这所有与key 相关联的副作用函数 effects
        let deps = depsMap.get(key)
        // 如果不存在,则新建一个Set 并与key 关联
        if(!deps) {
            depsMap.set(key, (deps = new Set())) 
        }

        // 将副作用函数添加到deps中
        deps.add(activeEffectPro)
    }

    function trigger(target, key) {
         // 根据target 从桶中取得depsMap , 它是 key ---> effects
        const depsMap = bucketPro.get(target)
        if(!depsMap) return 
        // 根据key 取得所有副作用函数 effects
        const effects = depsMap.get(key)
        // 执行副作用函数
        effects && effects.forEach(fn=>fn())
    }