美文网首页
Vue3响应式原理傻瓜式教程(二)——Proxy & Refle

Vue3响应式原理傻瓜式教程(二)——Proxy & Refle

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

    上一节我们学到了响应式的简单原理:Vue3响应式原理傻瓜式教程(一)——Reactive - 简书 (jianshu.com)

    这一节我们将学习,Vue3中是如何实现动态更新的。

    在Vue2中,用到defineProperty来实现自动更新,那么Vue3使用的是ProxyReflect

    Proxy & Reflect的简单定义

    1. 关于Proxy,引用一下阮一峰ES6教程里的话:

      Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

    2. 关于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源码~


    参考资料:
    Vue Mastery
    Proxy - ECMAScript 6入门 (ruanyifeng.com)

    相关文章

      网友评论

          本文标题:Vue3响应式原理傻瓜式教程(二)——Proxy & Refle

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