昨天我們解決了單一依賴所導致的指數增長問題。然而,在真實的開發場景中,一個 effect
函式往往需要依賴多個響應式變數,現在我們試著新增多個依賴,在範例中加入第二個響應式變數 count
,並讓 effect
同時依賴 flag
和 count
。按鈕的點擊事件只會修改 count
的值。
1<!doctype html>2<html lang="en">3 <head>4 <meta charset="UTF-8" />5 <title>Title</title>6 <style>7 body {8 padding: 150px;9 }10 </style>11 </head>12 <body>13 <button id="btn">按钮</button>14 <script type="module">15 import { ref, effect } from '../dist/reactivity.esm.js'16 collapsed lines
16
17 const flag = ref(true)18 const count = ref(0)19
20 effect(() => {21 flag.value22 count.value23 console.count('effect')24 })25
26 btn.onclick = () => {27 count.value++28 }29 </script>30 </body>31</html>
當 effect
函式同時依賴 flag
和 count
兩個響應式變數後,點擊按鈕觸發更新時,問題又回來了,依賴收集再次出現了指數級的增長,為什麼會這樣?
初始化
頁面第一次載入,effect
執行一次。它依序讀取 flag.value
和 count.value
,觸發兩次依賴收集。
flag
透過Link1
與effect
關聯。count
透過Link2
與effect
關聯。effect
自己的deps
鏈表也記錄Link1 -> Link2
。
到目前為止,都沒什麼問題。
點擊按鈕執行第一次
當按鈕點擊,count.value++
觸發更新,effect
的 run()
方法被呼叫。
- 執行
run()
:depsTail = undefined
。 this.deps
仍然指向Link1
。
(deps && !depsTail)
的狀態,就是我們用來判斷是否「正在重新執行」的關鍵點。
- 讀取
flag.value
,觸發第一次link()
effect
函式體重新執行,先讀取flag.value
,觸發link(flag, effect)
。link()
開始判斷:- 條件:
depsTail = undefined
、頭節點sub.deps
存在(sub.deps && !sub.depsTail)
成立。 - 接著檢查
sub.deps.dep === dep
(Link1
的dep
是否為flag
),成立。
- 條件:
- 複用成功,
depsTail
移到flag
的 link。
- 讀取
count.value
,觸發第二次link()
effect
繼續執行,讀取count.value
,觸發link(count, effect)
。link()
開始判斷:(sub.deps && !sub.depsTail)
不成立,因為剛剛depsTail
移到flag
的 link。- 複用檢查直接失敗,建立了一個全新的
Link3
節點,並且移動指針。
執行完,flag
的依賴 (Link1
) 被正確複用,但 count
的依賴被重複新增了節點 (Link2
存在,又新增了 Link3
)。
導致下一次點擊按鈕,propagate
就會觸發 effect
執行兩次(透過 Link2
和 Link3
),因此指數增長。
問題分析:為何只有第一個依賴被正確複用?
邏輯問題:
- 我們只檢查了
sub.deps
(頭節點),它永遠只拿effect
依賴鏈表的第一個節點來比較,導致只有第一個依賴能被複用。 - 一旦第一個依賴複用成功,
depsTail
就被賦值,後續的依賴檢查全部失敗。
今天我們ㄓ透過圖解,一步步追蹤了內部依賴鏈表的情況。分析後可以知道,問題的根源在於現有的節點複用邏輯存在漏洞:它只會檢查並比對依賴鏈表的第一個節點 (sub.deps
)。
明天我們將基於這次的問題解析結果,來實作解決方案。