嘿,日安!

Day 13 - Effect:多重依賴之節點復用解方

22nd September 2025
Front-end
Vue3
IT鐵人賽2025
從零到一打造 Vue3 響應式系統
Last updated:22nd September 2025
7 Minutes
1261 Words

昨天,我們知道當 effect 函式依賴多個響應式變數時,會觸發指數級更新。

觀察我們之前的做法:

  1. run() 函式首先會將 depsTail 設為 undefined

    effect.ts
    1
    run(){
    2
    const prevSub = activeSub
    3
    activeSub = this
    4
    5
    // 開始執行,讓尾節點變 undefined
    6
    this.depsTail = undefined
    7
    8
    ...
    9
    ...
    10
    }
  2. 後續的依賴收集過程中,depsTail 會被賦值並指向已複用的節點。

    1
    export function link(dep, sub){
    2
    const currentDep = sub.depsTail
    3
    if(currentDep === undefined && sub.deps){
    4
    if(sub.deps.dep === dep){
    5
    sub.depsTail = sub.deps //移動尾節點指針,指向剛剛復用的節點
    6
    return
    7
    }
    8
    }
    9
    ...
    10
    ...
    11
    12
    }
  3. 邏輯判斷

    1
    export function link(dep, sub){
    2
    const currentDep = sub.depsTail;
    3
    // 僅在 depsTail 為 undefined 時,才嘗試從頭節點 sub.deps 開始複用
    4
    if (currentDep === undefined && sub.deps) {
    5
    // 只會檢查依賴鏈表的第一個節點
    6
    if (sub.deps.dep === dep) {
    7
    sub.depsTail = sub.deps; // 成功後移動指針
    8
    return;
    9
    }
    10
    }
    11
    12
    // 若不符合上述條件,則直接建立新節點...
    13
    }

深入分析後,我們了解問題在依賴節點的複用邏輯上:

  • 檢查範圍過小:複用邏輯只檢查並比對依賴鏈表的第一個節點 (sub.deps)。
  • 指針提早退出:一旦第一個依賴(例如 flag)複用成功,depsTail 就被賦值。導致後續依賴(例如 count)在檢查時,因爲 currentDep === undefined 條件不成立,直接跳過複用檢查,盲目地建立了新的 Link 節點。

核心思路

舊的邏輯中,depsTail 只是一個標記,用來判斷是否是「第一次執行複用」。現在我們要把它升級為一個「進度指針」。它的作用是標記當前複用檢查進行到了鏈表的哪個位置

這個思考提供了一個關鍵點:「depsTail 存在時,代表依賴鏈表的遍歷與複用正在進行中。

一、實作:擴充檢查邏輯

因此,我們就可以在原有的 link 函式中,增加一個額外的檢查邏輯。

當第一個 if 條件不成立,但 depsTail (currentDep) 確實存在時,就意味著我們不應該從頭節點開始檢查,而應該從當前 depsTail 所在節點的下一個節點 (currentDep.nextDep) 繼續檢查。

default

依照上面的執行邏輯,flag 複用成功後,depsTail 指向 Link1。我們需要新增的邏輯,就是要從 Link1nextDep,也就是 Link2,繼續進行檢查。

檢查邏輯的核心:如果尾節點 (depsTail) 存在,並且這個尾節點還有下一個節點 (nextDep),我們就應該檢查這個 nextDep 是否是我們要找的目標,如果是,就直接複用它。

  • 確認狀態:檢查 depsTail 是否有值。如果有,代表開始復用,並且停在鏈表的某個節點上(像是 Link1)。
  • 尋找下個節點:我們的下一個目標,自然就是當前 depsTail 所指向節點的「下一個節點」,也就是 currentDep.nextDep (對應到我們的例子,就是 Link2)。
  • 進行比對:我們需要判斷這個節點所連接的依賴 (currentDep.nextDep.dep),是否就是我們當前正要處理的依賴 (count)。
  • 執行複用:如果比對成功,就將 depsTail 這個「進度指針」向前移動到這個 nextDep 節點上。
1
const currentDep = sub.depsTail
2
if(currentDep === undefined && sub.deps){
3
4
// 依賴鏈表頭節點的 ref 與當前要連接的 ref 相等的話,表示之前收集過依賴
5
if(sub.deps.dep === dep){
6
sub.depsTail = sub.deps //移動尾節點指針,指向剛剛復用的節點
7
return // 直接回傳,不新增節點
8
}
9
}else if(currentDep){ //尾節點存在
10
11
// 尾節點的 nextDep 所連接的 ref,等於當前要連接的 ref
12
if(currentDep.nextDep?.dep === dep){
13
sub.depsTail = currentDep.nextDep
14
//移動尾節點指針,復用尾節點的 nextDep
15
return
2 collapsed lines
16
}
17
}

二、重構與簡化

目前這個結構雖然已經能正確運行,但我們可以重構得更簡潔。我們發現無論是從頭開始還是從中途繼續,我們的目標都是找到「下一個待檢查節點」

1
const currentDep = sub.depsTail
2
// 相同邏輯:根據 currentDep 是否存在,來決定下一個要檢查的節點
3
const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep
4
// 如果 nextDep.dep 等於我當前要收集的 dep
5
if(nextDep && nextDep.dep === dep){
6
sub.depsTail = nextDep // 移動指針
7
return
8
}

完整執行流程

  1. 取得 depsTail 當前值(currentDep = sub.depsTail
  2. 根據 depsTail 決定要檢查哪個節點:
    • depsTailundefined → 從頭節點開始(nextDep = sub.deps
    • depsTail 有值 → 檢查下一個(nextDep = currentDep.nextDep
  3. 檢查是否可以復用:
    • nextDep 必須存在
      • dep:當前要新增鏈表節點的那個 ref
      • nextDep.dep 當前節點的下 一個節點,它所連結的 ref
  4. 如果可以復用:
    • 移動 depsTailnextDep(記錄遍歷進度)
    • 不建立新的 Link,提前返回

透過將 depsTail 指針從一個單純的「尾部標記」升級為「遍歷進度指針」,我們解決了多變數依賴下的節點複用問題。

重構後的程式碼不僅修復了指數級更新的 Bug,更用統一的邏輯處理了不同情況下的節點檢查。

Article title:Day 13 - Effect:多重依賴之節點復用解方
Article author:日安
Release time:22nd September 2025