美文网首页
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