深入理解 Vue 3 响应性原理 -- 实现数据响应式

介绍

接着 << 深入理解 Vue 3 响应性原理 – Set/Map/WeakMap 函数 >> 基础知识已经具备了,接着就是开始实现了

再看一下, 什么是响应式: 如果我们的一个数据改变了,Vue 知道怎么去更新模板以及会更新模板的计算机属性。

依赖关系 目标图和effects 集

目标图(“target map”),它的类型是 WeakMap
它储存了与每个“响应性对象属性”关联的依赖
depsMap 存储了每个属性的依赖,并且 dep 是一个 effects 集(Set)的依赖。
这些effect应该再值发生变化时重新运行。
请添加图片描述

//目标图存储着每个响应式对象的依赖
  const targetMap = new WeakMap()

  function track(target,key){
    //获取目标的 deps 图,在我们的例子中是 product
    let depsMap = targetMap.get(target)
    //如果它还不存在,我们将为这个对象创建一个新的deps图
    if(!depsMap){
      targetMap.set(target,(depsMap = new Map()))
    }
    //获得这个属性的依赖对象(quantity)
    let dep = depsMap.get(key)
    //如果它不存在,我们将创建一个新的 Set
    if(!dep){
      depsMap.set(key,(dep = new Set()))
    }
    //把 effect 添加到依赖中
    dep.add(effect)
  }
  function trigger(target,key){
    //检查此对象是否拥有依赖的属性
    const depsMap = targetMap.get(target)
    // 没有则直接返回
    if(!depsMap){return} 
    //否则,我们将检查此属性是否具有依赖
    let dep = depsMap.get(key)
    //dep 存在,遍历dep,运行每一个 effect
    if(dep){
      dep.forEach(effect => {effect()})
    }
  }

  let product = {price:5,quantity:2}
  let total = 0
  let effect = () =>{
    total = product.price * product.quantity
  }
  // 跟踪依赖
  track(product, 'quantity');
  // 首次触发副作用
  effect();
  console.log(total);
  product.quantity = 3
  // 触发函数 trigger 遍历我们存储了的每一个 effect
  trigger('quantity');
  console.log(total);

如何让代码自动实现响应式

换句话说: 我们该如何知道什么时候使用了 GET 或者 SET

Proxy(代理) & Reflect(反射)

  • 三种打印出对象的属性
    let product = {a: 1,b: 2} // product.a | product[a] | Reflect.get(product, 'a')

  • Proxy(代理)是另一个对象的占位符,默认情况下对该对象进行委托
    这段代码会先调用代理,代理再调用产品,然后产品再返回代理,最后这个产品被返回到控制台日志打印出“2”, 简单来说代理就是一个对象委托。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LbH8re0s-1651237558484)(./img/response/proxy代理关系.jpg)]

  • 在 Proxy 里使用 Reflect,我们会有一个附加参数,称为 receiver(接收器),它将传递到我们的 Reflect调用中。它保证了当我们的对象有继承自其它对象的值或函数时 this 指针能正确的指向使用(的对象)

    let product = {price: 5, quantity: 2} //product
    let proxiedProduct = new Proxy(product,{//proxiedProduct
        get(target,key,receiver){
        return Reflect.get(target,key,receiver)
      }
    }) 
    console.log(proxiedProduct.quantity)
    
  • 我们的set(方法)接收 target、key、value、和 receiver,我们将在 set 被调用时打印出我们的 key 和 value。然后我们再调用 Reflect.set,传递的参数是target、key、value 和 receiver。

    let product = {price: 5, quantity: 2} //product
    let proxiedProduct = new Proxy(product,{//proxiedProduct
        get(target,key,receiver){
        return Reflect.get(target,key,receiver)
      },
      set(target,key,value,receiver){
        return Reflect.set(target,key,value,receiver)
      }
    }) 
    //测试
    proxiedProduct.quantity = 4
    console.log(proxiedProduct.quantity)
    
  • 创建一个称为 reactive的函数, 用 handler 包装我们的 get 和 set方法 到常量处理程序中,最后我们将创建一个新的 Proxy,传递我们的 target 和我们的 handler

    function reactive(target){
      const handler = {
        get(target,key,receiver){
          return Reflect.get(target,key,receiver)
        },
        set(target,key,value,receiver){
          return Reflect.set(target,key,value,receiver)
        }
      }
      return new Proxy(target,handler)
    }
    // 现在,我们声明产品时,我们只需传递一个对象到响应式函数中  
    let product = reactive({price: 5, quantity:2 })
    product.quantity = 4
    console.log(product.quantity)
    

activeEffect & Ref

  • 如何只在 effect 里调用追踪函数

    // 引入 activeEffect 变量
    let activeEffect = null //它是现在正在运行中的 effect
    function effect(eff){//声明一个名为 effect 的函数,它接受一个匿名函数
      activeEffect = eff 
      activeEffect() 
      activeEffect = null //复位 activeEffect
    }
    // 这里就很巧妙了, 执行 effect 函数的时候, 会触发product.price 的get方法
    // 把当前 activeEffect 注册到 set 函数中
    effect(){
      total = product.price * product.quantity
    }
    
  • 和 Vue 3 响应性源代码中 ref 的使用原理
    首先先明白js中的计算属性

    let user = {
      firstName: 'Gregg',
      lastName: 'Pollack',
      //对象访问器是获取或设置值的函数,所以这里我们可以声明 get fullName,它返回一个名字和姓氏的组合字符串
      get fullName(){
        return `${this.firstName} ${this.lastName}`
      },
      //声明一个名为fullName 的 setter,它接受一个值,并把它分成两个不同的字符串,设置到名字和姓氏中
      set fullName(value){
        [this.firstName,this.lastName] = value.split(' ')
      },
    }
    console.log(`Name is ${user.fullName}`) //Name is Gregg Pollack
    user.fullName = 'Adam Jahr'
    console.log(`Name is ${user.fullName}`) //Name is Adam Jahr
    

    ref的实现

    function ref(raw){
      const r = {
        get value(){
          //调用跟踪函数,追踪我们正在创建的对象r,键是"value",然后返回原始值(传入值)
          track(r,'value')
          return raw
        },
        //setter 接收一个新值,把新值赋值给原始值(raw)
        set value(newVal){
          raw = newVal
          //调用触发函数
          trigger(r,'value')
        },
      }
      //返回对象
      return r
    }
    

toRef

class ObjectRefImpl {
      constructor(_object, _key) {
          this._object = _object;
          this._key = _key;
          this.__v_isRef = true;
      }
      get value() {
          return this._object[this._key];
      }
      set value(newVal) {
          this._object[this._key] = newVal;
      }
  }

完整代码

缺少 ref 的实现

const targetWeak = new WeakMap()
var activeEffect = null
function effect(eff) {
  activeEffect = eff
  activeEffect() // 很巧妙
  activeEffect = null // 复位
}

// 实现 track  跟踪依赖
function track(target, key) {
  if (activeEffect) {
    var depMap = targetWeak.get(target)
    if (!depMap) {
      targetWeak.set(target, (depMap = new Map()))
    }
    var dep = depMap.get(key)
    if (!dep) {
      // 为了跟踪依赖,我们将 effect 添加到 Set 中。使用 Set 是因为它不允许拥有重复值
      depMap.set(key, (dep = new Set()))
    }
    //
    dep.add(activeEffect)
  }
}

// 触发
function trigger(target, key) {
  var depMap = targetWeak.get(target)
  if (!depMap) return
  var dep = depMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

// reactive 绑定响应式

function reactive(target) {
  const handle = {
    get: function (obj, property, receiver) {
      console.log('get----reactive');
      const result = Reflect.get(obj, property, receiver)
      track(obj, property)
      return result
    },
    set: function (obj, key, value, receiver) {
      console.log('set----reactive', obj, target)
      const oldValue = obj[key]
      const result = Reflect.set(obj, key, value, receiver)
      if (oldValue !== result) {
        // 如果新值不等旧值
        trigger(obj, key)
      }
      return result
    }
  }

  return new Proxy(target, handle)
}
function ref(raw) {  // 这里使用的 js 原生的计算属性
  const r = {
    get value() {
      console.log('get----ref');
      // 调用跟踪函数,追踪我们正在创建的对象r,键是"value",然后返回原始值(传入值)
      track(r, 'value');
      return raw;
    },
    // setter 接收一个新值,把新值赋值给原始值(raw)
    set value(newVal) {
      console.log('set----ref');
      raw = newVal;
      // 调用触发函数
      trigger(r, 'value');
    }
  };
  // 返回对象
  return r;
}
var product = reactive({
  price: 3,
  nums: 10
})
var total = 0
// 总价格
effect(() => {
  total = product.price * product.nums
})

console.log(total, product)
product.price = 6
console.log(total, product)
console.log(targetWeak)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值