iT邦幫忙

2025 iThome 鐵人賽

DAY 7
1

什麼是 Dependency Tracking?

Dependency Tracking(依賴追蹤)是一種用於自動收集並記錄資料間依賴關係的技術,能夠讓系統在資料變動時精準地觸發重新計算或副作用,這種技術是實現精細響應式(fine-grained reactivity)的核心。

想像 Excel 試算表:當你更改一個儲存格,所有依賴這個儲存格的其他儲存格會自動重新計算,這正是 dependency tracking 所提供的直觀核心思想。

Dependency Tracking 的核心角色

Dependency Tracking 主要由以下三個角色構成:

  • Source(資料來源):基本的可變值(signal)。
  • Computed(計算值):基於 source 進行純函數推導的衍生值,具備快取與惰性求值。
  • Effect(副作用):實際執行 UI 更新、資料 fetch 等操作,屬於不可避免的外部影響。

這三者之間會形成明確的依賴圖:
https://ithelp.ithome.com.tw/upload/images/20250807/20129020ADuJcWzkJ6.png

Dependency Tracking 的運作原理

Dependency Tracking 的實現通常仰賴以下步驟:

收集(Tracking)

在執行 effect 或 computed 時,系統會透過 getter 收集目前正在取得的 source,記錄在一個 stack 或是有向圖(Graph)裡面,這裡會應用到一些基礎的 Data Structure,如果不懂的話後面章節會有補充。
範例是概念化的 Typescript:

export interface Computation {
    dependencies: Set<Set<Computation>>;
    execute: () => void;
}

const effectStack: Computation[] = [];

export function subscribe(current: Computation, subscriptions: Set<Computation>): void {
    subscriptions.add(running);
    current.dependencies.add(subscriptions);
}

function createSignal<T>(value: T) {
    const subscribers = new Set<Computation>();
    const getter = () => {
        const currEffect = effectStack[effectStack.length - 1];
        if (currEffect) subscribe(currEffect, subscribers);
        return value;
    };
    const setter = (newValue: T) => {
        if (newValue === value) return;
        value = newValue;
        subscribers.forEach(sub => sub.execute());
    };
    return { getter, setter };
}

通知(Notification)

當 source 變動時,會通知所有相關的 computed 和 effect 重新執行。
系統會透過 scheduler 排程更新:

function effect(fn: () => void) {
    const runner: Computation = {
        execute: () => {
            cleanupDependencies(running);
            runWithStack(running, fn);
        },
        dependencies: new Set(),
    };
    runner.execute();  // 確保 effect 運作
}

function cleanupDependencies(computation: Computation) {
    computation.dependencies.forEach((subscription) => {
        subscription.delete(computation);
    });
    computation.dependencies.clear();
}

export function runWithStack<T>(computation: Computation, fn: () => T): T {
    effectStack.push(computation);
    try {
        return fn(); // 執行 effect callback
    } finally {
        effectStack.pop(); // 確保 stack 運作
    }
}

排程(Scheduling)

Scheduler 可避免重複觸發、合併批次更新,進一步提升效能:

function schedule(job) {
  queueMicrotask(job);
}

Pull-based vs Push-based

前面已經有詳細講解過 Pull & Push 的差異了,這邊再透過圖表稍微複習一下:

類型 說明 實例
Pull-based UI 主動查詢資料變動(diffing) Virtual DOM(React)
Push-based 資料主動推送更新 Signals / MobX / Solid.js

Signals 類型的響應式系統通常採用 Push-based,透過顯式依賴圖,能精準地得知哪些節點需要更新,而避免大量 diffing。

動態依賴的處理

Dependency Tracking 最棘手的問題之一是動態依賴,例如條件式依賴:

effect(() => {
  if (userId()) {
    fetchProfile(userId());
  }
});

當條件改變時,系統需要清理舊的依賴關係,確保之後的更新不再追蹤無效的依賴。這依靠 runtime 提供的 cleanup 機制來完成。

各框架 Dependency Tracking 快速比較

框架 實現方式 Scheduler
Solid.js Runtime Stack-based microtask + batching
Vue 3 Runtime Proxy-based macro task (job queue)
MobX Runtime Global getter wrap microtask
Svelte Compile-time 靜態分析 同步或 microtask

React 的 dependency 模型詳解將在下一篇中深入介紹。

結語

Dependency Tracking 提供了精細響應式的核心架構,透過自動化的追蹤依賴關係,使系統能精準且高效地處理變動,掌握 Dependency Tracking,將幫助你在設計或優化前端架構時,有更強大的基礎。

下一篇文章中(Dependency Tracking 基本原理 (II)),我們將具體分析 React 中 dependency tracking 的運作方式與存在的問題,並以 Signals 的模型進行對比,探討如何改善這些問題。


上一篇
理解 Signal 運作原理
系列文
Reactivity 小技巧大變革:掌握 Signals 就這麼簡單!7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言