在解決了鏈表節點指數增長的問題後,我們還需要注意依賴的有效性。
effect 的執行路徑可能因為條件判斷或程式邏輯不同而改變,導致這次執行中不再需要某些依賴。
如果這些過期依賴沒有被清理:
因此,在收集依賴時,不只要能正確複用,也必須具備 清理過期依賴 的機制。
下面我們來說需要清理依賴的兩個狀況。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<style>
body {
padding: 150px;
}
</style>
</head>
<body>
<div id="app"></div>
<button id="flagBtn">update flag</button>
<button id="nameBtn">update name</button>
<button id="ageBtn">update age</button>
<script type="module">
// import { ref, effect } from '../../../node_modules/vue/dist/vue.esm-browser.js'
import { ref, effect } from '../dist/reactivity.esm.js'
const flag = ref(true)
const name = ref('姓名')
const age = ref(18)
effect(() => {
console.count('effect')
if(flag.value){
app.innerHTML = name.value
}else{
app.innerHTML = age.value
}
})
flagBtn.onclick = () => {
flag.value = !flag.value
}
nameBtn.onclick = () => {
name.value = '姓名' + Math.random()
}
ageBtn.onclick = () => {
age.value++
}
</script>
</body>
</html>
我們現在有三個變數,flag
、name
、age
flag
是 true,那點擊 name會觸發更新,但是點擊 age 不會觸發更新。flag
是 false,那點擊 age 會觸發更新,但是點擊 name 不會觸發更新。這很合理,因為沒有執行的狀況就不應該觸發。
effect: 1
、 flag
是 true。update flag
:effect 更新,輸出 effect: 2
、 flag
是 false。update name
:我們期望他沒有反應,因為「如果 flag
是 false,點擊 name 不會觸發更新。」effect: 3
。我們先來看一下 effect 以及 name 裡面的內容,執行步驟是初始化後先點擊 update flag
,再點擊 update name
。
const e = effect(() => {
console.count('effect')
if(flag.value){
app.innerHTML = name.value
}else{
app.innerHTML = age.value
}
})
...
...
nameBtn.onclick = () => {
name.value = '姓名' + Math.random()
console.dir(e)
console.log(name)
}
我們預期 effect 回傳值中,不會有 name
節點:實際上是正確的,目前 Link 節點是 flag
→ age
。
我們預期 name
回傳值中,不會有頭節點跟尾節點,但實際上輸出結果是自己是正在被訂閱的狀態。
我們現在了解異常情況是
effect
裡面訂閱 flag
、age
沒有 name,
是我們要的正確結果。name
裡面發現自己被 effect
訂閱,但鏈表上沒有它節點,它應該要被清除。遇到 flag
收集依賴,建立鏈表節點。
遇到 name
,建立鏈表節點。
點擊按鈕,effect 重新執行,run
函式讓depsTail
指向undefined
。
接著link
函式建立鏈表節點,先檢查是否可以復用,頭節點desp
存在depsTail == undefined
,可以復用。
發現可以復用,depsTail
指向 link1,此時 flag
是 false。
由於 flag
是 false,接下來遇到 age
,之前沒有收集依賴過,於是建立新的鏈表節點。
建立age
鏈表節點之後,depsTail
的 nextDep
以及 depsTail
指向新節點。
此時你看黃底色的地方,發現 effect
已經沒有存 link2,但是 link2 的 sub
仍然指向 effect。
所以才會出現 name
更新,仍然會讓 effect 執行的情況。
const flag = ref(true)
const name = ref('姓名')
const age = ref(18)
let count = 0
effect(() => {
console.count('effect')
if(count>0) return
count++
if(flag.value){
app.innerHTML = name.value
}else{
app.innerHTML = age.value
}
})
flagBtn.onclick = () => {
flag.value = !flag.value
}
nameBtn.onclick = () => {
name.value = '姓名' + Math.random()
}
ageBtn.onclick = () => {
age.value++
}
到時候修正會一起調整,所以我們先來說第二種需要清除依賴的狀況。
count>0
會返回,無論如何點擊按鈕就不再觸發)count
是 0
,繼續往下執行
count++
,count
現在是1。flag.value
→ 收集依賴:flag
。flag
是 true
,接著讀到 name.value
→ 收集依賴:name
。此時 Link 節點是
flag
→name
;depsTail
指向name
。
effect 被flag
與name
兩個 ref 訂閱。
run()
。depsTail
= undefined
effect
函式,馬上遇到 if (count > 0) return
被中斷,因此鏈表節點上沒有任何改動。遇到 return 程式應該會中斷,可是你一直點擊 name
按鈕,console.count('effect')
仍然一直觸發更新。
但遇到 if (count > 0) return
,effect 沒有存取任何依賴, deps 鏈表上應該要是空的,可是鏈表上面有之前建立的鏈表沒有清除。
此時鏈表上的狀態一直處於:有頭節點deps
,並且尾節點depsTail
= undefined
。
明天我們會來談怎麼清除這些依賴。
同步更新《嘿,日安!》技術部落格