美文网首页
Vue3响应式原理傻瓜式教程(三)——ActiveEffect

Vue3响应式原理傻瓜式教程(三)——ActiveEffect

作者: DebraJohn | 来源:发表于2022-04-10 18:47 被阅读0次

    上一节,我们学习到了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高级程序设计(第四版)》

    相关文章

      网友评论

          本文标题:Vue3响应式原理傻瓜式教程(三)——ActiveEffect

          本文链接:https://www.haomeiwen.com/subject/epavsrtx.html