Dependency Tracking(依賴追蹤)是一種用於自動收集並記錄資料間依賴關係的技術,能夠讓系統在資料變動時精準地觸發重新計算或副作用,這種技術是實現精細響應式(fine-grained reactivity)的核心。
想像 Excel 試算表:當你更改一個儲存格,所有依賴這個儲存格的其他儲存格會自動重新計算,這正是 dependency tracking 所提供的直觀核心思想。
Dependency Tracking 主要由以下三個角色構成:
這三者之間會形成明確的依賴圖:
Dependency 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 };
}
當 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 運作
}
}
Scheduler 可避免重複觸發、合併批次更新,進一步提升效能:
function schedule(job) {
queueMicrotask(job);
}
前面已經有詳細講解過 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 機制來完成。
框架 | 實現方式 | 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 的模型進行對比,探討如何改善這些問題。