小心遺漏的 watch 讓網頁爆炸

vue-watch-may-cause-a-memory-leak

最近注意到 watch 有一個有趣的現象,一個可能會挖坑的現象。

在某些特殊情境下,watch 的位置可能不會在元件最外層。

類似這種感覺:

<script setup>
import { ref, watch } from 'vue'

const data = ref(1);

setTimeout(() => {
  watch(data, (value) => {
    console.log('[ Comp ] data: ', value);
  })
}, 500);
</script>

先別吐槽這樣寫超廢,只是個簡單的示意啦。(ʘ ͜ʖ ʘ)

其實如果有這類需求,推薦使用 VueUse 提供的 watchPausablewatchTriggerable 這類進階版 watch,看起來更漂亮、更好維護。

我們知道正常情況下,元件 onUnmounted 後,watch 就會自動解除。

但是剛剛的那個例子,onUnmounted 後 watch 不會自動解除喲!ლ(╹◡╹ლ)

所以如果頁面中有很多這類 watch,很有可能讓網頁爆掉唷!o(≧∀≦)o

路人:「是在開心個毛線喔。(゚Д゚*)ノ」

這是因為如果在內層呼叫 watch,會讓 watch 與元件處在不同 effectScope,導致此結果。

該怎麼辦?(っ °Д °;)っ

解法也很簡單,勤奮一點手動解除。◝( •ω• )◟

<script setup>
import { ref, watch } from 'vue'

const data = ref(1);

let unwatch;
setTimeout(() => {
  unwatch = watch(data, (value) => {
    console.log('[ CompUnwatch ] data: ', value);
  })
}, 500);

onUnmounted(() => {
  console.log('[ onUnmounted ]');
  unwatch();
})
</script>

路人:「啊如果有很多 watch,這樣一個一個解除很累欸。Σ(ˊДˋ;)」

鱈魚:「…('◉◞⊖◟◉` ),先不探究這種 code 怎麼過得了 code review ,假設真的有很多 watch 要解除,可以用剛剛提到的關鍵字『effectScope』」


effectScope 可以用來捕捉範圍內的 reactive effects,所以可以把所有的 watch 收集起來統一解除。

例如像這樣:

<script setup>
import { ref, watch, onUnmounted, effectScope } from 'vue'

const scope = effectScope()
const data = ref(1);
const number = ref(0);

setTimeout(() => {
  scope.run(() => {
    watch(data, (value) => {
      console.log('[ CompScope ] data: ', value);
    })
  })
}, 500);

setTimeout(() => {
  scope.run(() => {
    watch(number, (value) => {
      console.log('[ CompScope ] number: ', value);
    })
  })
}, 1000);

onUnmounted(() => {
  console.log('[ onUnmounted ]');
  scope.stop();
})
</script>

世界再度回復和平!✧⁑。٩(ˊᗜˋ*)و✧⁕。

以上提到的範例可以在此連結查看

可以觀察到即使元件消失後,devtool 的 console 依舊持續跑出 log!(゚Д゚*)ノ

不過說的真,請不要這樣用 watch。…(›´ω`‹ )

總結 🐟

  • watch 不會自動解除的原因是在不同 effectScope
  • 使用 effectScope 可以統一解除 watch