Vue3响应式原理傻瓜式教程(三)——ActiveEffect
2022-04-10 本文已影响0人
DebraJohn
上一节,我们学习到了Vue3如何通过Proxy来更新计算结果:Vue3响应式原理傻瓜式教程(二)——Proxy & Reflect - 简书 (jianshu.com)
activeEffect的作用
上一节的代码中存在一个问题:
每次获取属性值的时候,都要通过track
来收集effect
,但实际上我们只需要在effect
内部调用track
函数即可。
所以,在之前代码的基础上,我们引入activeEffect
变量:
let product = reactive({ price: 5, quantity: 2 })
let total = 0
let salePrice = 0 // 添加一个salePrice变量,来检测get方法的调用情况
let activeEffect = null
function effect(eff)
activeEffect = eff
activeEffect()
activeEffect = null // 执行后就重置变量
}
effect(() => {
total = product.price * product.quantity
// 输出:[Get] price => 5
// 输出:[Get] quantity => 2
})
effect(() => {
salePrice = product.price * 0.9 // 输出:[Get] price => 5
})
并且改写track函数,让它只在执行effect
,activeEffect
被赋值的时候去追踪收集:
function track(target, key) {
if (activeEffect) { // 判断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) // 收集activeEffect
}
}
执行试试:
console.log(`更新前, total = ${total}, salePrice = ${salePrice}`)
// 输出:更新前, total = 10, salePrice = 4.5
product.quantity = 3
// 输出:[Set] quantity => 3
// 【因为price没有改变,只执行了第1个effect】
// 输出:[Get] price => 5
// 输出:[Get] quantity => 3
console.log(`更新后, total = ${total}, salePrice = ${salePrice}`)
// 输出:更新后, total = 15, salePrice = 4.5
product.price = 10
// 输出:[Set] price => 10
// 【因为price发生改变,两个effect都执行了】
// 输出:[Get] price => 10
// 输出:[Get] quantity => 3
// 输出:[Get] price => 10
console.log(`更新后, total = ${total}, salePrice = ${salePrice}`)
// 输出:更新后, total = 30, salePrice = 9
ref函数
上面的代码中,能不能通过salePrice来计算total呢?
effect(() => {
total = salePrice * product.quantity
})
答案是不能,因为salePrice不是一个响应式变量。
这里就可以引入Ref
概念了,ref可以接收一个参数并且返回一个响应的Ref
对象
首先需要了解对象的访问器属性accessor
,《JavaScript高级程序设计(第4版)》中是这样解释的:
访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。
function ref(raw) {
const r = {
get value() {
track(r, 'value') // 在get中记录计算方法
return raw // 并且返回传入的值
},
set value(newVal) {
if (newVal !== raw) {
raw = newVal // 更新值
trigger(r, 'value') // 触发计算
}
}
}
return r // 返回ref对象
}
接下来,我们用ref函数跑一下:
let product = reactive({ price: 5, quantity: 2 })
let total = 0
let salePrice = ref(0)
effect(() => {
total = salePrice.value * product.quantity // 通过salePrice来计算total
})
effect(() => {
salePrice.value = product.price * 0.9
})
console.log(`更新前, total = ${total}, salePrice = ${salePrice.value}`)
// 更新前, total = 9, salePrice = 4.5
product.quantity = 3
console.log(`更新后, total = ${total}, salePrice = ${salePrice.value}`)
// 更新后, total = 13.5, salePrice = 4.5
product.price = 10
console.log(`更新后, total = ${total}, salePrice = ${salePrice.value}`)
// 更新后, total = 27, salePrice = 9
虽然Vue中的源码可能会更复杂一些,但以上我们的ref方法就是Vue3实现ref的核心内容。下一节我们来更加深入解读Vue3的源码。
参考资料:
Vue Mastery
《Javascript高级程序设计(第四版)》