我們的響應式系統經過前幾天的努力,已經做得差不多,感覺可以加上一下 DOM 的互動,來進行簡單的測試。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<style>
body {
padding: 150px;
}
</style>
</head>
<body>
<button id="btn">按鈕</button>
<script type="module">
import { ref, effect } from '../dist/reactivity.esm.js'
const flag = ref(true)
effect(() => {
flag.value
console.count('effect')
})
btn.onclick = () => {
flag.value = !flag.value
}
</script>
</body>
</html>
看起來不太妙,目前 effect 出現按鈕指數觸發,這樣一定是不行。
我們預期每次點擊按鈕,effect
只會執行一次。但實際情況看起來不太妙。
從 console.count
的結果可以看到,effect
的執行次數隨著點擊呈現指數級增長。
我們來了解一下問題癥結點。
頁面載入時,effect
執行一次。在執行過程中,讀取了 flag.value
,觸發 getter 進行依賴收集。
系統會建立一個 link1 節點,將 effect
與 flag
關聯起來。到這裡都符合預期。
當按鈕第一次被點擊,flag.value
從 true
變為 false
,觸發了 setter。
setter 內的 propagate
函式開始遍歷 flag
的依賴鏈表。
propagate
執行 link1 中儲存的 effect.run()
。
effect
函式重新執行,又讀取 flag.value
。觸發了 getter。
此時問題出現了:執行完畢後,flag
的依賴鏈表結構如圖,現在有兩個節點 (link1, link2),但它們都指向同一個 effect
,他們分別儲存 activeSub
。
執行結束後的鏈表:
當按鈕又被點擊,flag.value
從 false
變為 true
,再次觸發 setter。
propagate
開始遍歷依賴鏈表。但這一次,鏈表上有兩個節點 (link1 和 link2)。
propagate 先執行 link1 中的 effect.run()
。effect 內部讀取 flag.value
,再次觸發依賴收集,建立了一個新的 link3 節點加到鏈表尾部。
propagate
接著執行 link2 中的 effect.run()
。effect 內部又一次讀取 flag.value
,觸發依賴收集,又建立了一個新的 link4 節點並加到鏈表尾部。
執行結束後的鏈表:
我們可以發現在觸發更新的時候,鏈表上的所有節點全部都會分別建立一個新的節點,因此發生了指數觸發 effect
的情況。
每次 Effect
重新執行時:
因此導致每次點擊按鈕,鏈表上的每一個 Link 都會建立新的 link,並且重複執行,造成指數級增長現象。
因為下個篇幅比較長就先講到這,大家要先理解問題的癥結點在哪,明天實作解決方案,才會知道為什麼要這樣做。
同步更新《嘿,日安!》技術部落格