---
url: /blog-vue/vue-watch-may-cause-a-memory-leak.md
description: 某些情境會讓 watch 不會自動解除，一起來看看吧！(ˊДˋ;)
---

![vue-watch-may-cause-a-memory-leak](/vue-watch-may-cause-a-memory-leak.webp){.cover}

# 小心遺漏的 watch 讓網頁爆炸

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

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

類似這種感覺：

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

const data = ref(1)

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

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

::: tip
其實如果有這類需求，推薦使用 VueUse 提供的 [watchPausable](https://vueuse.org/shared/watchPausable/)、[watchTriggerable](https://vueuse.org/shared/watchTriggerable/#watchtriggerable) 這類進階版 watch，看起來更漂亮、更好維護。
:::

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

但是剛剛的那個例子，`onUnmounted` 後 `watch` 不會自動解除喲！%ლ(╹◡╹ლ)%

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

路人：「是在開心個毛線喔。%(ﾟДﾟ\*)ﾉ%」

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

## 該怎麼辦？%(っ °Д °;)っ%

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

```vue
<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](https://vuejs.org/api/reactivity-advanced.html#effectscope) 可以用來捕捉範圍內的 reactive effects，所以可以把所有的 watch 收集起來統一解除。

例如像這樣：

```vue
<script setup>
import { effectScope, onUnmounted, ref, watch } 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>
```

世界再度回復和平！%✧⁑｡٩(ˊᗜˋ\*)و✧⁕｡%

以上提到的範例可以在[此連結查看](https://play.vuejs.org/#eNrNVctu2zAQ/JWFLlYKRXbQ9uLaAdoihxToA01yqnJQ5JWtVCIJPpwUhv69S1Jy5ES2EbdBexN3h7sz3CG1Ct4LES8NBuNgojJZCA0KtRGnCSsqwaWGFUjMoYZc8goGBB2sUx95JZp4PLQLW2nwbiN/kXGBGyAX6UFesbtUZ4sNbBNr0QnLOFMaloUqbkqEqeUW5mmp8Chhk6FXQNxpobESZaqRVgCTxcnparXeV9eTIUVcpmDCUMXjis+wnCZBg0kC0L8EUiBbYPbzht8ngd/gVC+Pi3wDPHxIesm7EK3UXsxk2KEeRIFWJDov5vGt4ozmtLJliBbVKUqUX4Uu6FCSYAwuY3NpWfK7Ty6mpcGojTspPfFbRerG9PFNokK5JCrrnE7lHLVPn118wXv6Xifp0IwlviP5HRUvjeXoYR8MmxHtDs6xPXdGKNj8Up3da2SqFWWJWmTt8HRYBu0RbpP+QPd1/MbtS1hNp9jac4/RI3CTiYCzK1ZxwzTOIsA8x0z7uW7ehNaRNCPKTbvIkCzpk7NUp41XT46cjan1ZVEhNzoMj2B66vm71qFFRxAu09LgQw7A1uIlxiWfh4Mf/vJdu9pjGETg8VSdJFPnOoK3o5Hv1tHSbfeoYAcF1wO709ZJWG5YZs8ZjKBmJMvvto1j15SkfU71IpYpm/GK8q/gZDSy+xup51RTErTbvC1mYZHFO667rvCsWLoPcNJdaOhjj2/MxivzPwzcR5mpblA28WY0/UZwxWNpWDe4zx+9DvES+m3ijfLULgdw8tKezcpv28Or9cbhPm7JK82Fd9wfGhsa5v/Q/+4I912Czu/zgGuwxflPX7MSNRjfitb99mnytPHZr1z7t3zZx25N8a845CVH35zH9uHXvwG30kPy)。

::: danger
可以觀察到即使元件消失後，devtool 的 console 依舊持續跑出 log！%(ﾟДﾟ\*)ﾉ%
:::

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

## 總結 🐟

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