首頁 > 軟體

Vue2響應式系統之巢狀

2022-04-13 04:00:23

1、場景

在 開發中肯定存在元件巢狀元件的情況,類似於下邊的樣子。Vue

<!-- parent-component -->
<div>
  <my-component :text="inner"></my-component>
  {{ text }}
<div>

<!-- my-component-->
<div>{{ text }}</div>

回到我們之前的響應式系統,模擬一下上邊的情況:

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    text: "hello, world",
    inner: "內部",
};
observe(data);

const updateMyComponent = () => {
    console.log("子元件收到:", data.inner);
};

const updateParentComponent = () => {
    new Watcher(updateMyComponent);
    console.log("父元件收到:", data.text);
};

new Watcher(updateParentComponent);

data.text = "hello, liang";

可以先 分鐘考慮一下上邊輸出什麼?1

首先回憶一下 會做什麼操作。new Watcher

第一步是儲存當前函數,然後執行當前函數前將全域性的 賦值為當前 物件。Dep.targetWatcher

接下來執行 函數的時候,如果讀取了相應的屬性就會觸發 ,從而將當前 收集到該屬性的 中。gettergetWatcherDep

2、執行過程

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    text: "hello, world",
    inner: "內部",
};
observe(data);

const updateMyComponent = () => {
    console.log("子元件收到:", data.inner);
};

const updateParentComponent = () => {
    new Watcher(updateMyComponent);
    console.log("父元件收到:", data.text);
};

new Watcher(updateParentComponent);

data.text = "hello, liang";

我們再一步一步理清一下:

  • new Watcher(updateParentComponent);

將 賦值為儲存了 函數的 。Dep.targetupdateParentComponentWatcher

接下來執行 函數。updateParentComponent

  • new Watcher(updateMyComponent);

將 賦值為儲存了 函數的 。Dep.targetupdateMyComponentWatcher

接下來執行 函數。updateMyComponent

const updateMyComponent = () => {
    console.log("子元件收到:", data.inner);
};

// 讀取了 inner 變數。
// data.inner 的 Dep 收集當前 Watcher(儲存了 `updateMyComponent` 函數)
const updateParentComponent = () => {
    new Watcher(updateMyComponent);
    console.log("父元件收到:", data.text);
};
// 讀取了 text 變數。
// data.text 的 Dep 收集當前 Watcher (儲存了 `updateMyComponent` 函數)

data.text = "hello, liang";

觸發 的 函數,執行它依賴的 ,而此時是 函數。textsetWatcherupdateMyComponent

所以上邊程式碼最終輸出的結果是:

子元件收到: 內部  // new Watcher(updateMyComponent); 時候輸出
父元件收到: hello, world // new Watcher(updateParentComponent); 時候輸出
子元件收到: 內部 // data.text = "hello, liang"; 輸出

然而子元件並不依賴 ,依賴 的父元件反而沒有執行。data.textdata.text

3、修復

上邊的問題出在我們儲存當前正在執行 時候使用的是單個變數 。WatcherDep.target = null; // 靜態變數,全域性唯一

回憶一下學習 語言或者組合語言的時候對函數引數的處理:C

function b(p) {
    console.log(p);
}

function a(p) {
    b("child");
    console.log(p);
}

a("parent");

當函數發生巢狀呼叫的時候,執行 函數的時候我們會先將引數壓入棧中,然後執行 函數,同樣將引數壓入棧中, 函數執行完畢就將引數出棧。此時回到 函數就能正確取到 引數的值了。abbap

對應於 的收集,我們同樣可以使用一個棧來儲存,執行函數前將 壓入棧,執行函數完畢後將 彈出棧即可。其中, 始終指向棧頂 ,代表當前正在執行的函數。WatcherWatcherWatcherDep.targetWatcher

回到 程式碼中,我們提供一個壓棧和出棧的方法。Dep

import { remove } from "./util";

let uid = 0;

export default class Dep {
    ... 省略
}
Dep.target = null; // 靜態變數,全域性唯一

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
const targetStack = [];

export function pushTarget(target) {
    targetStack.push(target);
    Dep.target = target;
}

export function popTarget() {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1]; // 賦值為棧頂元素
}

然後 中,執行函數之前進行入棧,執行後進行出棧。Watcher

import { pushTarget, popTarget } from "./dep";
export default class Watcher {
    constructor(Fn) {
        this.getter = Fn;
        this.depIds = new Set(); // 擁有 has 函數可以判斷是否存在某個 id
        this.deps = [];
        this.newDeps = []; // 記錄新一次的依賴
        this.newDepIds = new Set();
        this.get();
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get() {
      /************修改的地方*******************************/
        pushTarget(this); // 儲存包裝了當前正在執行的函數的 Watcher
       /*******************************************/
        let value;
        try {
            value = this.getter.call();
        } catch (e) {
            throw e;
        } finally {
          /************修改的地方*******************************/
            popTarget();
          /*******************************************/
            this.cleanupDeps();
        }
        return value;
    }
   ...
}

4、測試

回到開頭的場景,再來執行一下:

import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
    text: "hello, world",
    inner: "內部",
};
observe(data);

const updateMyComponent = () => {
    console.log("子元件收到:", data.inner);
};

const updateParentComponent = () => {
    new Watcher(updateMyComponent);
    console.log("父元件收到:", data.text);
};

new Watcher(updateParentComponent);

data.text = "hello, liang";

執行 的時候將 入棧。new Watcher(updateParentComponent);Watcher

進入 函數,執行 的時候將 入棧。updateParentComponentnew Watcher(updateMyComponent);Watcher

執行 函數, 收集當前 ,執行完畢後 出棧。updateMyComponentdata.innerDep.targetWatcher

繼續執行 函數, 收集當前 。updateParentComponentdata.textDep.target

此時依賴就變得正常了, 會觸發 函數,從而輸出如下:data.textupdateParentComponent

子元件收到: 內部
父元件收到: hello, world
子元件收到: 內部
父元件收到: hello, liang

5、總結

今天這個相對好理解一些,通過棧解決了巢狀呼叫的情況。

到此這篇關於Vue2響應式系統之巢狀的文章就介紹到這了,更多相關Vue2 巢狀內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com