浅析一下Vue3的响应式原理

目录
  • Proxy
  • Reflect
  • 举个例子
  • reactive
  • effect
  • track
  • trigger

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响应式原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue3.0数据响应式原理详解

    基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理 预备知识 ES6 Proxy,整个响应式系统的基础. 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档. 先把Vue3.0跑起来 先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可. 下面一个简单计数器的DEMO: <!DOCTY

  • 详解vue3 响应式的实现原理

    目录 核心设计思想 Vue.js 2.x 响应式 Vue.js 3.x 响应式 依赖收集:get 函数 派发通知:set 函数 总结 源码参考 核心设计思想 除了组件化,Vue.js 另一个核心设计思想就是响应式.它的本质是当数据变化后会自动执行某个函数,映射到组件的实现就是,当数据变化后,会自动触发组件的重新渲染.响应式是 Vue.js 组件化更新渲染的一个核心机制. Vue.js 2.x 响应式 我们先来回顾一下 Vue.js 2.x 响应式实现的部分: 它在内部通过 Object.defi

  • 一文详解Vue3响应式原理

    目录 回顾 vue2.x 的响应式 vue3的响应式 Reflect 回顾 vue2.x 的响应式 实现原理: 对象类型:通过object.defineProperty()对属性的读取.修改进行拦截(数据劫持) 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹) Object.defineProperty(data,'count ",{ get(){}, set(){} }) 存在问题: 新增属性.删除属性,界面不会更新 直接通过下标修改数组,界面不会自动更新 但是

  • vue3.0响应式函数原理详细

    目录 1.reactive 2.ref 3.toRefs 4.computed 前言: Vue3重写了响应式系统,和Vue2相比底层采用Proxy对象实现,在初始化的时候不需要遍历所有的属性再把属性通过defineProperty转换成get和set.另外如果有多层属性嵌套的话只有访问某个属性的时候才会递归处理下一级的属性所以Vue3中响应式系统的性能要比Vue2好. 接下来我们自己实现Vue3响应式系统的核心函数(reactive/ref/toRefs/computed/effect/trac

  • 详解Vue3的响应式原理解析

    目录 Vue2响应式原理回顾 Vue3响应式原理剖析 嵌套对象响应式 避免重复代理 总结 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const originalProto = Array.prototype const arrayProto = Object.create(originalProto) ;['push', 'pop', 'shift', 'unshift', 'splic

  • 浅析一下Vue3的响应式原理

    目录 Proxy Reflect 举个例子 reactive effect track trigger Proxy Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作. const obj = { a: 1 }; const p = new Proxy(obj, {   get(target, property, receiver) {     console.log("get");     return Reflect.get(tar

  • Vue3 Reactive响应式原理逻辑详解

    目录 前言 一.怎么实现变量变化 二.怎么实现变量变化 三.将多个dep存储在Map中 四.将多个object的depsMap继续存储起来 五.核心 六.源码解析(TypeScript) 前言 本篇文章主要讲解vue响应式原理的逻辑,也就是vue怎么从最开始一步步推导出响应式的结构框架. 先从头构建一个简单函数推导出Vue3的Reactive原理,最后再进行源码的验证. 一.怎么实现变量变化 怎么实现变量变化,相关依赖的结果也跟着变化 当原本price=5变为price=20后total应该变为

  • Vue3 的响应式和以前有什么区别,Proxy 无敌?

    前言 大家都知道,Vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截它的原型方法来实现响应式. 举个例子: let vm = new Vue({ data() { return { a: 1 } } }) // ❌ oops,没反应! vm.b = 2 let vm = new Vue({ data() { return { a: 1 } }, watch: { b() { console.log('change !!') } } }) // ❌ oo

  • vue2.0/3.0的响应式原理及区别浅析

    前言 自从vue3.0正式上线以来,好多小伙伴都转战vue3.0了,这里把我自己总结的3.0和2.0的原理以及他俩的区别写出来,方便自己学习. step 一,vue2.0的响应式原理 先看看官网的解释: 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty把这些属性全部转为 getter/setter.Object.defineProperty 是 ES5 中一个无法 shim 的特性

  • 关于Vue3中的响应式原理

    目录 一.简介 二.响应核心 1.核心源码 2.逐步分析上述示例代码 3.收集依赖和触发依赖更新 三.V3.2的响应式优化 四.后话 一.简介 本章内容主要通过具体的简单示例来分析Vue3是如何实现响应式的.理解本章需要了解Vue3的响应式对象.只注重原理设计层面,细节不做太多讲解. 二.响应核心 1.核心源码 export class ReactiveEffect<T = any> { //是否激活 active = true //依赖列表 deps: Dep[] = [] // can b

  • vue3 reactive响应式依赖收集派发更新原理解析

    目录 proxy 依赖收集 currentEffect 派发更新 总结 proxy vue3的响应式实现依旧是依赖收集与派发更新,本节乃至后面涉及的代码都是经过简化,文章目的是讲解原理,直接贴源码会很枯燥 vue3已经从Object.property更换成Proxy,Proxy相比于前者可以直接监听对象数组,对于深层次的对象和数组,会把触发对应getter,然后去递归进行依赖收集,并不是直接像vue2暴力那样递归,总体而言性能更好 对reactive传进来的对象进行Proxy进行劫持在内部进行依

  • Vue3.0 响应式系统源码逐行分析讲解

    前言 关于响应式原理想必大家都很清楚了,下面我将会根据响应式API来具体讲解Vue3.0中的实现原理, 另外我只会针对get,set进行深入分析,本文包含以下API实现,推荐大家顺序阅读 effect reactive readonly computed ref 对了,大家一定要先知道怎么用哦~ 引子 先来段代码,大家可以直接复制哦,注意引用的文件 <!DOCTYPE html> <html lang="en"> <head> <meta ch

随机推荐