浅析一下Vue3的响应式原理

 更新时间:2022年08月11日 14:10:04   作者:王岳  
这篇文章主要通过示例和大家一起浅析一下Vue3的响应式原理,文中的示例代码讲解详细,对我们学习Vue3有一定帮助,需要的可以参考一下

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

如上例子,我们用 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);
  },
});

举个例子

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

<div id="app"></div>

<script>
  // 创建一个响应式对象
  const state = reactive({ counter: 1 });

  // 立即运行一个函数,当响应式对象的属性发生改变时重新执行。
  effect(() => {
    document.querySelector("#app").innerHTML = state.counter;
  });

  // 2s 后视图更新
  setTimeout(() => {
    state.counter += 1;
  }, 2000);
</script>

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

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

  • 调用 reactive() 返回一个 Proxy 代理对象,并劫持对象的 get 与 set 操作
  • 调用 effect() 方法时,会访问属性 state.counter,此时会触发 proxy 的 get 操作。
  • get 方法会调用 track() 进行依赖收集;建立一个对象(state)、属性(counter)、effect 副作用函数的依赖关系;
  • 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); // 依赖更新
    },
  });
};

effect

let activeEffect;
function effect(fn) {
  const _effect = function reactiveEffect() {
    activeEffect = _effect;
    fn();
  };

  _effect();
}

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

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

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

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

[ // map 集合
  {
    key: {counter: 1} // state 对象,
    value: [ // map 集合
      {
        key: "counter",
        value: [ // set
          function reactiveEffect() {} // effect 副作用函数
        ],
      }
    ],
  },
];

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

trigger

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

setTimeout(() => {
  // 给 counter 属性赋值会触发 set 操作
  state.counter += 1;
}, 2000);
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const effects = depsMap.get(key);
  effects && effects.forEach((effect) => effect());
}

以上就是浅析一下Vue3的响应式原理的详细内容,更多关于Vue3响应式原理的资料请关注脚本之家其它相关文章!

相关文章

  • vue-swiper的使用教程

    vue-swiper的使用教程

    swiper是我之前做前端页面会用到的一个插件,我自己认为是非常好用的。这篇文章给大家介绍vue-swiper的使用教程,感兴趣的朋友一起看看吧
    2018-08-08
  • ant design vue的table取消自带分页问题

    ant design vue的table取消自带分页问题

    这篇文章主要介绍了ant design vue的table取消自带分页问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • 对Vue3中reactive的深入理解

    对Vue3中reactive的深入理解

    这篇文章主要介绍了对Vue3中reactive的深入理解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • vue+element ui实现锚点定位

    vue+element ui实现锚点定位

    这篇文章主要为大家详细介绍了vue+element ui实现锚点定位,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • Element中select多数据加载优化的实现

    Element中select多数据加载优化的实现

    本文主要介绍了Element中select多数据加载优化的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 基于vue-cli3+typescript的tsx开发模板搭建过程分享

    基于vue-cli3+typescript的tsx开发模板搭建过程分享

    这篇文章主要介绍了搭建基于vue-cli3+typescript的tsx开发模板,本文通过实例代码截图的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • 在Vue中实现地图热点展示与交互的方法详解(如热力图)

    在Vue中实现地图热点展示与交互的方法详解(如热力图)

    随着大数据和可视化技术的发展,地图热点展示越来越受到人们的关注,在Vue应用中,我们通常需要实现地图热点的展示和交互,以便更好地呈现数据和分析结果,本文将介绍在Vue中如何进行地图热点展示与交互,包括热力图、点聚合等
    2023-07-07
  • Vue生态的新成员Pinia的详细介绍

    Vue生态的新成员Pinia的详细介绍

    本文主要介绍了Vue生态的新成员Pinia的详细介绍,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • 测试平台开发vue组件化重构前端代码

    测试平台开发vue组件化重构前端代码

    这篇文章主要为大家介绍了测试平台开发vue组件化重构前端代码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • vue如何实现表单多选并且获取其中的值

    vue如何实现表单多选并且获取其中的值

    这篇文章主要介绍了vue如何实现表单多选并且获取其中的值问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07

最新评论