iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Vue.js

從零到一打造 Vue3 響應式系統系列 第 14

Day 14 - Effect:清理依賴的場景

  • 分享至 

  • xImage
  •  

banner

在解決了鏈表節點指數增長的問題後,我們還需要注意依賴的有效性。

effect 的執行路徑可能因為條件判斷或程式邏輯不同而改變,導致這次執行中不再需要某些依賴。

如果這些過期依賴沒有被清理:

  • 會造成 記憶體洩漏:不需要的鏈表節點一直被保留。
  • 會導致 不必要的更新:effect 雖然已經不依賴某個 ref,但這個 ref 的變化仍然會觸發 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>

我們現在有三個變數,flagnameage

  • 如果 flag 是 true,那點擊 name會觸發更新,但是點擊 age 不會觸發更新。
  • 如果 flag 是 false,那點擊 age 會觸發更新,但是點擊 name 不會觸發更新。

這很合理,因為沒有執行的狀況就不應該觸發。

實際情況

day14-01

  • 初始化:輸出 effect: 1flag是 true。
  • 點擊 update flag:effect 更新,輸出 effect: 2flag是 false。
  • 點擊 update name:我們期望他沒有反應,因為「如果 flag 是 false,點擊 name 不會觸發更新。」
  • 但 effect 仍然更新,console 輸出 effect: 3

問題說明

我們先來看一下 effect 以及 name 裡面的內容,執行步驟是初始化後先點擊 update flag,再點擊 update name

檢查 Effect 回傳值

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)
}

day14-02

我們預期 effect 回傳值中,不會有 name節點:實際上是正確的,目前 Link 節點是 flagage

day14-03

我們預期 name 回傳值中,不會有頭節點跟尾節點,但實際上輸出結果是自己是正在被訂閱的狀態。

異常情況

我們現在了解異常情況是

  • effect 裡面訂閱 flagage 沒有 name,是我們要的正確結果。
  • name 裡面發現自己被 effect 訂閱,但鏈表上沒有它節點,它應該要被清除。

Effect 依賴清理圖解問題說明

頁面初始化

day14-04

遇到 flag 收集依賴,建立鏈表節點。

day14-05

遇到 name,建立鏈表節點。

點擊按鈕,觸發更新

day14-06

點擊按鈕,effect 重新執行,run 函式讓depsTail 指向undefined

day14-07

接著link函式建立鏈表節點,先檢查是否可以復用,頭節點desp存在depsTail == undefined,可以復用。

day14-08

發現可以復用,depsTail 指向 link1,此時 flag 是 false。

day14-09

由於 flag 是 false,接下來遇到 age,之前沒有收集依賴過,於是建立新的鏈表節點。

day14-10

建立age鏈表節點之後,depsTailnextDep 以及 depsTail 指向新節點。

day14-11

此時你看黃底色的地方,發現 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 會返回,無論如何點擊按鈕就不再觸發)

初始化

  • 此時count0,繼續往下執行
    • count++count現在是1。
    • 讀到 flag.value → 收集依賴:flag
    • flagtrue,接著讀到 name.value → 收集依賴:name

此時 Link 節點是 flagnamedepsTail 指向 name
effect 被 flagname 兩個 ref 訂閱。

day14-12

  • 點擊 name 按鈕:
    • 觸發 effect 重新 run()
    • depsTail = undefined
    • 進入 effect 函式,馬上遇到 if (count > 0) return被中斷,因此鏈表節點上沒有任何改動。

遇到 return 程式應該會中斷,可是你一直點擊 name按鈕,console.count('effect') 仍然一直觸發更新。

day14-13

但遇到 if (count > 0) return,effect 沒有存取任何依賴, deps 鏈表上應該要是空的,可是鏈表上面有之前建立的鏈表沒有清除。

此時鏈表上的狀態一直處於:有頭節點deps,並且尾節點depsTail = undefined

明天我們會來談怎麼清除這些依賴。


同步更新《嘿,日安!》技術部落格


上一篇
Day 13 - Effect:多重依賴之節點復用解方
系列文
從零到一打造 Vue3 響應式系統14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言