Vue3响应式原理原来是这样的

439

Vue3响应式原理

  • 使用proxy进行代理
  • 使用weakMap作为依赖的存储
  • track Func中对desMap进行跟踪
    • 存在,拿到依赖的map(里面存放的是所有key和它对应的依赖收集器),找到当前key的依赖收集器,为当前key的依赖收集器添加effect(副作用依赖),如果当前key没有依赖收集器,为当前key的创建依赖收集器,并添加effect。
    • 不存在,给对象创建一个依赖map。为当前key的创建依赖收集器,并添加effect。
  • 然后触发trigger Func 对target对象是否拥有依赖的属性进行判断,---没有,直接return。---有的话遍历依赖,运行每个effect。

预想:

  • 当我们想要获取到响应式的值,自动call track func来得到想要的数据
  • 当我们改变响应式中的值,自动call trigger func 来进行set

Q:我们怎么来能拦截到这个set和set?

A:vue3中使用ES6的reflect和proxy来进行拦截。

proxy和reflect理解和他们是怎么工作的

proxy

Proxy 用于修改某些操作的默认行为,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

  • 通过 new Proxy(target,handler)
  • target:设置拦截层的目标对象
  • handler参数:也是一个对象,用来定制拦截行为\
 class A {
        constructor(name, age, hobby) {
          this.name = name;
          this.age = age;
          this.hobby = hobby;
        }
      }
      var aa = new A("ly", 11, "eat");
      var proxyObj = new Proxy(aa, {
        get(target, key) {  //target:代理的对象,此处是aa, key:我们要获取的属性,此处是name
          console.log("get"); //get 在获取proxyObj.name时候触发
          return target[key];
        },
        set(target, key, value) {
          console.log("set"); //set 对proxyObj.name属性从新赋值的时候触发

          target[key] = value + 1;
        },
      });
      proxyObj.name = "lily";
			// 对我更改的name进行了改写操作
      console.log(`proxyObj.name:${proxyObj.name}`);//proxyObj.name:lily1
      console.log(`aa.name:${aa.name}`);//aa.name:lily1 原对象也会发生变化

targetMap

所有代理的对象与他们的key所对应依赖存放的地方。

      const targetMap = new WeakMap();

track(依赖收集)

track就像是proxy中handler中的get方法。

在track的时候,会进行我们所熟知的依赖收集,会将当前activeEffect添加到dep里面,而说起这一类的关系。它会有一个一对多对多的关系。

从代码看也非常的清晰,首先我们会有一个总的targetMap它是一个WeakMapkeytarget(代理的对象), value是一个Map,称之为depsMap,它是用于管理当前target中每个keydeps也就是副作用依赖,也就是以前熟知的depend。在vue3中是通过Set(确保每个依赖唯一)来去实现的。

 function track(target,key) {
            let depsMap = targetMap.get(target)
            // 查询targetMap中target是否有存在依赖map
            // 不存在,创建一个存放依赖的map对象
            if(!depsMap){
                targetMap.set(target,(depsMap= new Map()))
            }
            // depsMap存在,查找depMap中key是否有依赖收集器,没有则创建
            let dep= depsMap.get(key)
            // dep不存在
            if(!dep){
                depsMap.set(key,(dep=new Set()))
            }
            // dep存在,添加effect(副作用依赖)
            dep.add(effect)
            
        }

trigger(响应触发)

trigger 就像是proxy中handler的set方法

function trigger(target,key) {
            const depsMap=targetMap.get(target)
            // 查找当前target(响应式对象)是否有依赖map
            // 没有直接返回不做任何操作
            if(!depsMap) return 
            // 存在,则找当前key的依赖收集器
            let depsMap = depsMap.get(key)
            // 执行所有的effect(副作用依赖)
            if(dep){
                dep.forEach(effect=>effect())
            }
        }

effect 副作用依赖

当响应式的值发生变化,effect会得到通知并且触发。

 let effect = () => {
          total = products.quantity*quantity.price
      }

proxy

 const product = { quantity: 2, price: 5 };
      const proxyProduct = new Proxy(product, {
        get(target, key, receiver) {
          console.log("get was called with key = " + key);
          // Reflect.get该方法是用来读取一个对象的属性。
          // 参数如下解析:
          // target: 目标对象
          // name: 是我们要读取的属性。
          // receiver(可选): 可以理解为上下文this对象。
          //   在proxy中使用reflect.get(),确保该key是在this上,避免vue2可能出现的不必要的报错
          return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
          console.log("set was called with key = " + key, "value = " + value);
          // 上面的get方法是获取对象中的值,那么set就是设置该对象的属性值了,参数解析简单如下:
          // target: 我们需要操作的对象。
          // name: 我们需要设置该对象的属性名。
          // value: 我们要设置的属性值。
          // receiver: 可以理解为上下文this对象。如果我们在设置值的时候遇到setter函数,该参数就指向与setter中上下文this对象。
          // 该函数会返回一个Boolean的值,代表在目标对象上设置属性是否成功。
          return Reflect.set(target, key, value, receiver);
        },
      });
      proxyProduct.quantity = 4;
      console.log(proxyProduct.quantity);

让它们更像vue3源码

现在我们有了track、trigger、effect,也知道了vue3中是使用proxy实现的响应式。现在将它们组装起来叭。

  function reactive(target) {
        const handler = {
          get(target, key, receiver) {
            let result = Reflect.get(target, key, receiver);
            // 添加依赖
            track(target, key);
            return result;
          },
          set(target, key, value, receiver) {
            let oldValue = target[key];
            let result = Reflect.set(target, key, value, receiver);
            // 如果oldValue!==value(新的),那就要调用trigger来触发每个effect进行更改
            if (oldValue !== value) {
              trigger(target, key);
            }
            return result;
          },
        };
        return new Proxy(target, handler);
      }

      let product = reactive({ price: 5, quatity: 2 });

使用一下

   let product = reactive({ price: 5, quatity: 2 });
  let products = reactive({ price: 5, quantity: 2 });
      let total = 0;
      let effect = () => {
        total = products.quantity * products.price;
      };
      effect();
      console.log(total); // 10
      products.quantity = 3;
      console.log(total); // 15 ---响应起作用了!

ref如何实现的响应式

      function ref(raw){
          const r={
              get value(){
                  track(r,'value')
                  return raw
              },
              set value(newVal){
                  raw = newVal 
                  trigger(r,'value')
              }
          }
          return r
      }
let price = ref(0)
console.log(price.value) //0
price.value=10
console.log(price.value) // 10