VUE3中的响应式简易实现

78 阅读1分钟

实现VUE3中的简易式响应系统

<script>
    const targetMap = new WeakMap()
    const proxyMap = new WeakMap()

    let activeEffect = null

    function track(target, key) {
        if (activeEffect) {
            let depsMap = targetMap.get(target)
            if (!depsMap) {
                targetMap.set(target, (depsMap = new Map()))
            }
            let dep = depsMap.get(key)
            if (!dep) {
                depsMap.set(key, (dep = new Set()))
            }
            dep.add(activeEffect)
        }
    }
    function trigger(target, key) {
        const depsMap = targetMap.get(target)
        if (!depsMap) return
        const dep = depsMap.get(key)
        if (dep) {
            dep.forEach(effect => effect())
        }
    }

    function reactive(target) {
        if (proxyMap.has(target)) return proxyMap.get(target)
        const proxy = new Proxy(target, {
            get(target, key, receiver) {
                track(target, key)
                return Reflect.get(target, key, receiver)
            },
            set(target, key, value, receiver) {
                let oldValue = target[key]
                const result = Reflect.set(target, key, value, receiver)
                if (oldValue !== value) {
                    trigger(target, key)
                }
                return result
            }
        })
        proxyMap.set(target, proxy)
        return proxy
    }
    function effect(fn) {
        const effectFn = () => {
            activeEffect = effectFn
            fn()
            activeEffect = null
        }
        effectFn()
    }

    const state = reactive({ cnt: 1, name: 'nothing' })
    effect(() => { console.log(`cnt: ${state.cnt}`) })
    effect(() => { console.log(`name: ${state.name}`) })
    state.cnt++
    state.name = 'anything'
</script>

这里的targetMap的结构大致如下

WeakMap {
  { cnt: 2, name: 'anything' } => Map { 
    "cnt" => Set { effect1 },
    "name" => Set { effect2 }
  }
}

weakMap.png
红框代表depsMap其中一个item绿框代表depsMapvalue,也就是Set中的一个item

track函数用来追踪依赖,当某个属性被读取的时候,将当前关联的副作用函数加入该属性的依赖列表。 例如上面的state.cnt,首次执行effect函数的时候(line58)执行一次get,然后会执行track函数,此时targetMap内的数据如下

WeakMap {
  { cnt: 1 } => Map {
    "cnt" => Set { effect1 }
  }
}

当执行state.cnt++的时候,此时触发set拦截,调用trigger函数,然后执行effectFn,此时再次访问state.cnt,触发get函数,调用track函数。

问题1:为什么在Proxy里面要调用Reflect

而不直接return target[key] 或 target[key] = value
答:确保方法调用时,this仍然指向原对象。同时在set方法中Reflect可以返回bool值。