Vue3响应式简单实现

623 阅读1分钟

参考:

Vue3响应式原理juejin.cn/post/685666…

Vue3响应式原理剖析juejin.cn/post/686439…

github:github.com/vuejs/vue-n…

proxy能解决Object.defineProperty的以下问题:

  • 要递归遍历对象,消耗大
  • 数组响应化需要额外实现
  • 新加或删除属性无法监听
  • map,set无法响应式
建立响应式数据 */function reactice(obj){}/* 
声明响应函数cb(依赖响应式数据) */function effect(cb){}/* 
依赖收集:建立 数据&cb 映射关系 */function track(target,key){}/* 
触发更新:根据映射关系,执行cb */function trigger(target,key){}

实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <button id="btn">click </button>
    <script src="./xiao-reactivity.js"></script>
    <script>
        const root = document.getElementById('app')
        const btn = document.getElementById('btn')

        const o = {
            name:'kkb',
            age:1
        }
        let obj = reactive(o)
        effect(()=>{
            // 数据的修改,会通知这个函数执行
            root.innerHTML = `
                <h1>${obj.name}今年${obj.age}岁了</h1>
            `
        })
        btn.addEventListener('click',()=>{
            obj.age+=1
        },false)
    </script>
</body>
</html>
let targetMap = new WeakMap()
let effectStack = []//存储effect
function reactive(data){
    return new Proxy(data,{
        get(target,key){
            track(target,key)
            return target[key]
        },
        set(target,key,value){
            target[key] = value
            trigger(target,key)
        }
    })
}
//收集依赖
// targetMap: WeakMap{ 
//     target:Map{ 
//         key: Set[cb1,cb2...] 
//     }
// }
function track(target,key){
    const effect = effectStack[effectStack.length-1]
    if(effect){
        let depsMap = targetMap.get(target)
        if(!depsMap){
            depsMap = new Map()
            targetMap.set(target,depsMap)
        }
        let deps = depsMap.get(key)
        if(!deps){
            deps = new Set()
            depsMap.set(key,deps)
        }
        if(!deps.has(effect)){
            deps.add(effect)
        }
    }
}
//执行依赖
function trigger(target,key){
    targetMap.get(target)?.get(key)?.forEach(effect=>effect())
}
//声明依赖
function effect(fn){
    if(effectStack.indexOf(fn)===-1){//如果不加这个,有些effect语句访问target.name和target.age,队列会有两个相同的effect
        try{
            effectStack.push(fn)//程序执行到effect时推进任务栈
            return fn() //执行effect,自然读取到代理对象,从而把这个effect存到相应key的依赖中,也可以不return,反正finally都会执行的,而且下面没其他代码了
        }finally{
            effectStack.pop() //清理任务栈
        }
    }
    
}