上一节我们学到了响应式的简单原理:Vue3响应式原理傻瓜式教程(一)——Reactive - 简书 (jianshu.com)
这一节我们将学习,Vue3中是如何实现动态更新的。
在Vue2中,用到defineProperty
来实现自动更新,那么Vue3使用的是Proxy
和Reflect
。
Proxy & Reflect的简单定义
-
关于Proxy,引用一下阮一峰ES6教程里的话:
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
-
关于Reflect,我们都知道,获取对象的某一属性的值可以用
object.key
或者object[key]
那么在ES6中,通过
Reflect.get(target, key)
也可以获取到:let product = { price: 5, quantity: 2 } Reflect.get(product, 'price') // 5
那么,二者结合,就形成了一个代理器:
const obj = new Proxy({}, {
// 为了避免执行上下文指向出错,这里的receiver指的是 Proxy 或者继承 Proxy 的对象
get: function (target, propKey, receiver) {
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
return Reflect.set(target, propKey, value, receiver);
}
});
在Vue3中的运用
我们用上一节的例子来封装一下Proxy:
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
console.log('[Get]', key, '=>', Reflect.get(target, key, receiver))
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('[Set]', key, '=>', value)
return Reflect.set(target, key, value, receiver)
}
})
}
let product = reactive({ price: 5, quantity: 2 })
product.price = 6
// 设置price会执行代理器set方法 输出:[Set] price => 6
console.log(product.price) // 6
// 读取price会执行代理器get方法 输出:[Get] price=> 6
上一节的例子中,我们调用track
来收集计算方法,在属性值改变时,再通过调用trigger
来触发已收集的方法,但是需要我们手动去调用这些方法。
let product = { price: 5, quantity: 2 }
let total = 0
let effect = () => {
total = product.price * product.quantity
}
const targetMap = new Map()
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.add(effect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return // 如果没有任何依赖,直接返回
let dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
effect() // 初始化total = 10
// 需要手动调用方法
track('product', 'price') // 收集方法
product.price = 6 // total目前还是10
trigger('product', 'price') // 手动触发,total变为12
那么这一节,我们通过Proxy
,就可以实现自动调用:
function reactive(target) {
const handler = {
get(target, key, receiver) {
let result = Reflect.get(target, key, receiver)
console.log('[Get]', key, '=>', result)
track(target, key) // 读取值的时候,收集计算方法
return result
},
set(target, key, value, receiver) {
console.log('[Set]', key, '=>', value)
let oldValue = target[key]
let result = Reflect.set(target, key, value, receiver)
if (oldValue !== result) {
trigger(target, key) // 设置值的时候,如果value有改变,触发调用已收集的方法
}
return result
}
}
return new Proxy(target, handler)
}
我们把product
对象进行代理,然后调用:
product = reactive({ price: 5, quantity: 2 })
effect()
// 初始化 分别获取了price和quantity,并进行结果计算, 触发两次Get,并且自动调用track收集计算方法
// 输出:[Get] price => 5
// 输出:[Get] quantity => 2
product.price = 6
// 修改price值,触发Set,并且自动调用trigger执行收集的计算方法
// 输出:[Set] price => 6
// 因为此处又调用到了effect,所以又触发两次Get
// 输出:[Get] price => 6
// 输出:[Get] quantity => 2
console.log(total) // 12 结果自动更新了
以上,就是Vue3自动更新的大体实现思路,下一节内容会再优化几处代码,然后继续深入Vue3源码~