Updates and Cleanup
Shelf gets a ReadingTimer that counts the seconds you spend on a book. It works. Then testers open and close it twenty times, and the app crawls: twenty timers are still ticking, each updating state no one can see. Vue removed the component, but not the things it started. Cleanup is your job.
The rule
Whatever you start on mount, you stop on unmount.
<!-- ReadingTimer.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const seconds = ref(0)
let timerId
function onKey(e) {
if (e.key === ' ') seconds.value = 0
}
onMounted(() => {
timerId = setInterval(() => seconds.value++, 1000)
window.addEventListener('keydown', onKey)
})
onUnmounted(() => {
clearInterval(timerId)
window.removeEventListener('keydown', onKey)
})
</script>
<template>
<p>Reading for {{ seconds }} s</p>
</template>Vue cleans up what Vue created: the component's elements, its listeners written with @, its computeds and watchers. It does not know about things you started with browser APIs: timers from setInterval or setTimeout, listeners added to window or document, open network connections, or third-party libraries attached to an element. Those need a matching stop in onUnmounted (or onBeforeUnmount, which runs a moment earlier while the DOM is still in place).
Note that the listener must be the same function in both calls. removeEventListener('keydown', (e) => ...) with a new arrow function removes nothing, because it is a different function.
Update hooks. onBeforeUpdate and onUpdated run around each re-render caused by a state change. onUpdated is the moment the DOM reflects the new state, useful for things like scrolling a chat to its newest message. Two cautions: it runs after any change in the component, and changing state inside it triggers another update, which can loop forever. Most "do something when X changes" jobs belong in a watcher instead (next lesson).
The shortcut
Use pairs. Every time you write a "start" line in onMounted, write its "stop" line in onUnmounted right away.
setInterval
Start:
timerId = setInterval(...). Stop:clearInterval(timerId).
Drills
Answer fast — trust the rule.
A component starts setInterval in onMounted and never clears it. What happens after it is removed?
Which of these must you clean up yourself?
onUnmounted(() => window.removeEventListener('keydown', (e) => handle(e))) does not remove the listener. Why?
Why is changing state inside onUpdated risky?
Remember
onUnmountedis where you undo timers, window listeners and outside libraries;onUpdatedruns after each re-render.- Shortcut: write every start and stop as a pair.
- Trap: removing a listener with a different function, and changing state in
onUpdated.
