三歲小朋友也能懂的 Vue watch 清理副作用 (clean up)

三歲小朋友也能懂的 Vue watch 清理副作用 (clean up)

1. watch 是什麼?

小朋友,你有沒有玩過「聽寫遊戲」呢?
當老師說一個字,你就要寫下來對不對?

在 Vue 裡,watch 就像是在「聽」一個變數的變化。
只要變數變了,它就會「做一些事情」,例如去網路上抓新的圖片。

2. 什麼是「過期的副作用」?

現在想像一下,你在畫畫,老師叫你畫「小貓」,你開始畫。
但畫到一半,老師又說:「換畫小狗!」
然後又說:「換畫小兔子!」
結果,你的畫紙上畫滿了不同動物,亂七八糟的,對吧?

在 Vue 也是這樣:

  • watch 監聽一個變數

  • 當變數變了,它就會去「網路抓資料」

  • 但是如果變數一直變,就會同時發出很多請求,最後收到的結果可能是錯的!

這就是「過期的副作用」,因為早先請求的資料已經沒用了,但它還是回來影響畫面。

3. 怎麼解決?用 onInvalidate

現在,想像老師給你一個橡皮擦,每次說「換畫小狗」,你就先擦掉「小貓」。
這樣你的畫紙上就不會亂七八糟,只有最新的畫。

在 Vue 裡,我們用 onInvalidate 來做這件事:

jsCopyEditwatch(message, async (newVal, oldVal, onInvalidate) => {
  let expired = false  // 這是標記,表示「舊的畫要擦掉」
  onInvalidate(() => expired = true)  // 這裡當新資料來時,舊的就過期

  let res = await new Promise(resolve => {
    setTimeout(() => {
      console.log('call api')
      resolve('新的圖片網址')
    }, 3000) // 模擬 API 請求
  })

  if (!expired) {  // 只有「沒有過期」的時候才更新畫面
    image.value = res
  }
})

4. onInvalidate 怎麼運作?

  • message 改變時,watch 會執行一次函式

  • 如果 message 又變了,onInvalidate 會讓上次的請求「失效」

  • 這樣舊的結果不會影響新的結果!

5. AbortController 進階版本

除了用 expired 這個標記,我們還可以用 AbortController 來取消請求:

jsCopyEditwatch(message, async (newVal, oldVal, onInvalidate) => {
  const controller = new AbortController()
  const signal = controller.signal

  onInvalidate(() => {
    console.log('取消舊請求')
    controller.abort()  // 取消請求
  })

  try {
    let response = await fetch('https://picsum.photos/300/300', { signal })
    if (!response.ok) throw new Error('錯誤')

    image.value = response.url
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('請求被取消了!')
    }
  }
})

這樣,每次新的請求來時,舊的請求就會被「砍掉」,不會占用資源。


🎨 小結:

  • watch 就像在「聽變數」,變了就做事

  • 但如果變數變太快,會有「過期的副作用」

  • onInvalidate 就像橡皮擦,讓舊的請求不影響新的

  • AbortController 甚至可以直接「砍掉」網路請求

這樣 Vue 就不會亂七八糟,畫面只會顯示「最新」的結果! 🎉

請也幫我解釋 onCleanup onWatcherCleanup 謝謝!

想像你在玩積木,你有一座「變色城堡」,當你按下不同的按鈕,城堡會變成不同的顏色。但有時候,你按得太快,還沒變色完成就又按下一個顏色,結果顏色亂跳,甚至可能變成奇怪的顏色組合。

Vue 的 watch 也會遇到類似的問題:

當我們監聽某個變數的變化(比如 message),如果變化太快,但 API 回應比較慢,就可能發生「過期的結果蓋掉新結果」的問題,這叫做 競態問題(Race Condition)。

onInvalidate(onCleanup 的舊名字)

這時候我們可以用 onInvalidate 來「取消」舊的請求。簡單來說,就是當你按下一個新顏色時,先把上一次的變色過程停止,確保最後的結果是最新的。

Vue 3.5 新增的 onWatcherCleanup

  • onCleanup 適用於一般的 watch,用來清理舊的副作用(像是 API 請求、計時器)。

  • onWatcherCleanup 適合用在 封裝好的函式庫,當 watch 被銷毀時會自動清理。

差異:

  • onCleanup 用在 每次數值變更時,清理舊的影響。

  • onWatcherCleanup 則是當 整個監聽行為結束(例如元件被卸載)時,做一次性的清理。

類比:

  • onCleanup當你換顏色時,先擦掉舊的顏色

  • onWatcherCleanup當你不玩這座城堡時,把變色機關全部拆掉

這樣就能確保你的變色城堡不會變得亂七八糟啦!🎨✨