Vue3中reactive实现响应式的原理和实现方法

首先我们知道,Vue2中实现响应式的根本是使用了Object.definePropery,在初始化的时候给data对象中的每一个属性名都添加一个对应的get和set在它读写的时候调用,但这暴露了一个问题,就是没法在对整个对象的实现监听,也就是在给一个对象添加属性或者删除一个属性的时候,因为初始化函数不会调用,所以也就不会被get和set捕获到,于是想要后续追加属性被监听到需要调用vm实例身上的set方法,但是在Vue3运用了ES6中的Proxy代理完美解决了这个问题。

Proxy是一个构造函数,是实现对一整个对象的监听而不是defineProperty的对属性名监听,在创建时传入两个参数,第一个是需要被代理的对象,第二个是代理的配置对象(至少要传一个空对象,否则会报错),而这个对象中可以除了get/set还有一个deleteProperty,会在代理对象中的属性值被删除时调用,同时代理对象中被追加一个属性的时候也会调用set,如图

图中的形参target和key分别是要读取的这个对象和要读取的属性名,而value则是返回要修改的值或是新赋的值。由于是代理,没有跟原对象的直接对接,所以这里搭配了Reflect中的方法去原对象中进行操作,get返回的是读取到的值,而set和delete则是返回true/false判断是否修改成功。

基于以上两点,实现reactive实现的第一步

   function reactive (obj) {
      return new Proxy(obj, {
        get (target, key) {
          return Reflect.get(target, key)
        },
        set (target, key, value) {
          return Reflect.set(target, key, value)
        }
      })
    }

然后创建一个effect(依赖)函数对其进行初始化

//首先effect需要接受一个函数并将其保存在自身方便之后调用
//全局变量activeEffect存储每一个激活状态的effect以便对其追加属性
let activeEffect 
function effect(fn) {
  //这里则创建一个构造函数
  //经过初始化过后把这个fn保存在实例化对象上也就是_effect身上
  //然后原型上拥有一个run函数主要在被调用时可以把当前的状态给保存下来 然后把最开始传入的fn的执行结果返回出去
  const _effect = new ReactiveEffect(fn)

  _effect.run()
}

class ReactiveEffect {
  constructor(fn) {
    this._fn = fn
  }

  run() {
    activeEffect = this
    return this._fn()
  }
}

这个时候num = 17应该没什么问题,因为effect在调用时候触发了ReactiveEffect的实例化对象身上的run方法,执行了传入的fn

const obj = reactive({num:15})
let num
effect(()=>{
  //这里的obj.age会触发一次get操作
  num = obj.age + 2
})
console.log(17)

但是如果再让obj.age = 60的话,num不会再有变化,因为fn没有再次调用,num的值也就没有被刷新,所以这时候就需要一个拥有以下两点功能的方法,get触发时收集所有依赖,set触发时调用所有收集到的依赖,这样所有用到该值的地方都会发生更新。

如图所示,在get和set中分别加入一个函数

   function reactive (obj) {
      return new Proxy(obj, {
        get (target, key) {
          track(target,key)
          return Reflect.get(target, key)
        },
        set (target, key, value) {
          trigger(target,key)
          return Reflect.set(target, key, value)
        }
      })
    }

添加了一个track和trigger,如下

//创建一个对象用于存储所有产生依赖的对象,
const targetMap = new Map()
//把当前的值传进去
function track(target, key) {  
  //根据对象的指针地址找到其对应的存储空间
  let depsMap = targetMap.get(target)
  //第一次depsMap没有值,则需要对其进行初始化
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  //这里根据其属性地址取出对应的容器
  let dep = depsMap.get(key)
  //同上,也是不存在就对其进行初始化
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  //调用这个函数是为了把当前依赖于该属性的值存储进去,如果已经有了则返回不执行
  trackEffects(dep)
    //这个函数是为了把当前所收集到的依赖,保存到其对应的属性容器下,也就是将一开始传入的fn函数保存在了以obj.
    function trackEffects(dep) {
      //这里activeEffect是先前创建的全局变量,就犹如v-for循环时给函数传入index就能准确找到触发函数的那一项
      if (dep.has(activeEffect)) return
         dep.add(activeEffect)
         activeEffect.deps.push(dep)
    }
}

//依赖收集之后,一旦值发生改变,则会更新其被依赖的所有值
function trigger(target, key) {
  //取出对象容器
  let depsMap = targetMap.get(target)
  //取出属性容器
  let dep = depsMap.get(key)
  //传入更新所有依赖于该属性的值
  triggerEffects(dep)
    
    function triggerEffect(dep){
        for(const effect of dep){
            //执行所有存储的依赖,如以上那个例子就是执行num = obj.age + 2
            effect.run()
        }
    }
}

此时就实现了Vue3中reactive的雏形,后续添加还可以实现ref代理对象,computed计算属性等功能

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值