• 浅析 Vue3 响应式原理


    本 文 作 者 为   3 6 0   技 术 中 台效能工程 部 的 前 端 开 发 工 程 师

    Proxy

    Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作。

    `const obj = { a: 1 };
    const p = new Proxy(obj, {
      get(target, property, receiver) {
        console.log("get");
        return Reflect.get(target, property, receiver);
      },
      set(target, property, value, receiver) {
        console.log("set");
        return Reflect.set(target, property, receiver);
      },
      has(target, prop) {
        console.log("has");
        return Reflect.has(target, prop);
      },
      deleteProperty(target, prop) {
        console.log("deleteProperty");
        return Reflect.deleteProperty(target, prop);
      },
    });
    
    p.a; // 输出 --> get
    p.a = 2; // 输出 --> set
    "a"  in p; // 输出 --> has
    delete p.a; // 输出 --> deleteProperty` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如上例子,我们用 Proxy 代理了 Obj 对象的属性访问、属性赋值、in 操作符、delete 的操作,并进行 console.log 输出。

    Reflect

    Reflect 是与 Proxy 搭配使用的一个 API,当我们劫持了某些操作时,如果需要再把这些操作反射回去,那么就需要 Reflect 这个 API。

    由于我们拦截了对象的操作,所以这些操作该有的功能都丧失了,例如,访问属性 p.a 应该得到 a 属性的值,但此时却不会有任何结果,如果我们还想拥有拦截之前的功能,那我们就需要用 Reflect 反射回去。

    `const obj = { a: 1 };
    const p = new Proxy(obj, {
      get(target, property, receiver) {
        console.log("get");
        return Reflect.get(target, property, receiver);
      },
      set(target, property, value, receiver) {
        console.log("set");
        return Reflect.set(target, property, receiver);
      },
      has(target, prop) {
        console.log("has");
        return Reflect.has(target, prop);
      },
      deleteProperty(target, prop) {
        console.log("deleteProperty");
        return Reflect.deleteProperty(target, prop);
      },
    });` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    举个例子

    以下全文我们都会通过这个例子来讲述 Vue3 响应式的原理。

    `
    `
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们用 reactive 创建了一个响应式对象 state,并调用了 effect 方法,该方法接受一个副作用函数,effect 的执行会立即调用副作用函数,并将 state.counter 赋值给 #app.innerHTML;两秒后,state.counter += 1,此时,effect 的副作用函数会重新执行,页面也会变成 2.

    内部的执行过程大概如下图所示:

    1. 调用 reactive() 返回一个 Proxy 代理对象,并劫持对象的 get 与 set 操作

    2. 调用 effect() 方法时,会访问属性 state.counter,此时会触发 proxy 的 get 操作。

    3. get 方法会调用 track() 进行依赖收集;建立一个对象(state)、属性(counter)、effect 副作用函数的依赖关系;

    4. set 方法会调用 trigger() 进行依赖更新;通过对象(state)与属性(coutner)找到对应的 effect 副作用函数,然后重新执行。

    reactive

    reactive 会返回如下一个 Proxy 对象

    `const reactive = (target) => {
      return new Proxy(target, {
        get(target, key, receiver) {
          const res = Reflect.get(target, key, receiver);
    
          track(target, key); // 收集依赖
    
      if (isObject(res)) {
      // 如果当前获取的属性值是一个对象,则继续将为此对象创建 Proxy 代理
      return reactive(res);
     }
    
      return res;
     },
    
      set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);
     trigger(target, key); // 依赖更新
     },
     });
    };` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    effect

    `let activeEffect;
    function  effect(fn) {
      const _effect = function  reactiveEffect() {
        activeEffect = _effect;
        fn();
      };
    
      _effect();
    }` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    首先定义全局的 activeEffect,它永远指向当前正在执行的 effect 副作用函数。effect 为 fn 创建一个内部的副作用函数,然后立即执行,此时会触发对象的 get 操作,调用 track() 方法。

    `effect(() => {
      // effect 的立即执行会访问 state.counter,触发了对象的 get 操作。
      document.querySelector("#app").innerHTML = state.counter;
    });` 
    • 1
    • 2
    • 3
    • 4

    track

    track 会建立一个 对象(state) => 属性(counter) => effect 的一个依赖关系

    `const targetMap = new WeakMap();
    function  track(target, key) {
      if (!activeEffect) {
        return;
      }
    
      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()));
      }
    
      if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
      }
    }` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    执行完成成后我们得到一个如下的数据结构:

    `[ // map 集合
      {
        key: {counter: 1} // state 对象,
        value: [ // map 集合
          {
            key: "counter",
            value: [ // set
              function reactiveEffect() {} // effect 副作用函数
            ],
          }
        ],
      },
    ];` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意:当我们调用 effect 时,会将当前的副作用函数赋值给全局的 activeEffect,所以此时我们可以正确关联其依赖。

    trigger

    当我们给 state.counter 赋值的时候就会触发代理对象的 set 操作,从而调用 trigger 方法

    `setTimeout(() => {
      // 给 counter 属性赋值会触发 set 操作
     state.counter += 1;
    }, 2000);` 
    • 1
    • 2
    • 3
    • 4
    `function  trigger(target, key) {
      const depsMap = targetMap.get(target);
      if (!depsMap) return;
    
      const effects = depsMap.get(key);
      effects && effects.forEach((effect) => effect());
    }` 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    论文解读《Measuring and Relieving the Over-smoothing Problem for Graph NeuralNetworks from the Topological View》
    c++day4
    计算机操作系统:实验3 【虚拟存储器管理】
    回答 4 个读者高频问题,换作你怎么回答?
    VUE3照本宣科——应用实例API与setup
    SpringBoot--使用@RequestHeader获取请求头
    vue的双向绑定的原理,和angular的对比
    phpexcel 安装流程
    测试工程师面试题,你都遇到过哪些呢?
    Docker绑定CPU
  • 原文地址:https://blog.csdn.net/m0_57042151/article/details/126554209