首頁 >web前端 >Vue.js >Vue3的響應式原理是什麼

Vue3的響應式原理是什麼

王林
王林轉載
2023-05-24 17:55:231341瀏覽

Proxy

Proxy這個核心API被Vue3的響應式原理所依賴,利用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.

內部的執行過程大概如下圖所示:

Vue3的響應式原理是什麼

  • 呼叫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的響應式原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除