Vue3 的响应式采用ES6 的新增API Proxy 语法实现了对目标对象的数据劫持,在getter中使用track函数收集 effect(副作用),在setter中使用trigger函数执行收集的依赖。 接下来从一个实际用例开始,手把手实现一个乞丐版本的reactivity库,实现了 reactive, track, trigger, effect, ref, computed 等API
非响应式的JS代码
let product = { price: 5, quantity: 2 }
let total = product.price * product.quantity
console.log('total: ' + total) // 10
product.price = 10
console.log('total: ' + total) // 10, 由于product对象不具备响应式的能力,product.price变了,但是total还是10,而不是20
改造第一步
定义一个effect函数专门处理total计算的副作用,执行track函数把effect进行收集, trigger函数执行目标对象的effect, 更新total的值
let product = { price: 5, quantity: 2 }
let total = 0
// 专门用于计算total的值
let effect = () => {total = product.price * product.quantity
}
// 使用WeakMap作为存储容器,WeakMap的key必须是对象
const targetMap = new WeakMap()
function track(target, key) {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使用Set数据结构,去除添加重复的effectdep.add(effect)
}
function trigger(target, key) {let depsMap = targetMap.get(target)if (!depsMap) return;let dep = depsMap.get(key)if (dep) {dep.forEach(effect => {effect()})}
}
// 追踪 product.price的变化
track(product, 'price')
// 初始化执行一次
effect()
console.log('total: ' + total) // 10
product.price = 10
// 触发product.price 的保存的effect, 重新执行effect,更新total函数
trigger(product, 'price')
console.log('total: ' + total) // 20
改造第二步
实现reactive函数,使用proxy 代理target,在get中自动执行track的对应的key的effect, 在set中trigger执行对应key的effect
function reactive(target) {let handler = {get(target, key, receiver) {let result = Reflect.get(target, key, receiver)// 执行取值操作追踪track(target, key)return result},set(target, key, receiver) {let oldV