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函数,让它只在执行effectactiveEffect被赋值的时候去追踪收集:

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高级程序设计(第四版)》

上一篇下一篇

猜你喜欢

热点阅读