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

116 阅读6分钟

介绍

接着 << 深入理解 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);
12345678910111213141516171819202122232425262728293031323334353637383940414243444546

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

换句话说: 我们该如何知道什么时候使用了 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 = {price5quantity2//product
    let proxiedProduct = new Proxy(product,{//proxiedProduct
       get(target,key,receiver){
       return Reflect.get(target,key,receiver)
    }
    }) 
    console.log(proxiedProduct.quantity)
    1234567
    
  • 我们的set(方法)接收 target、key、value、和 receiver,我们将在 set 被调用时打印出我们的 key 和 value。然后我们再调用 Reflect.set,传递的参数是target、key、value 和 receiver。

    let product = {price5quantity2//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)
    123456789101112
    
  • 创建一个称为 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({price5quantity:2 })
    product.quantity = 4
    console.log(product.quantity)
    123456789101112131415
    

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
    }
    123456789101112
    
  • 和 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
    123456789101112131415
    

    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
    }
    1234567891011121314151617
    

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;
    }
}
12345678910111213

完整代码

缺少 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 = {
   getfunction (obj, property, receiver) {
     console.log('get----reactive');
     const result = Reflect.get(obj, property, receiver)
     track(obj, property)
     return result
  },
   setfunction (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({
 price3,
 nums10
})
var total = 0
// 总价格
effect(() => {
 total = product.price * product.nums
})
​
console.log(total, product)
product.price = 6
console.log(total, product)
console.log(targetWeak)