Wat zijn Lifecycle Hooks?
Met lifecycle hooks kun je code uitvoeren op een specifiek moment in het leven van een component. In de praktijk gebruik je er maar twee:
onMounted— als het component verschijnt (data ophalen, focus zetten)onUnmounted— als het component verdwijnt (timers stoppen, listeners verwijderen)
Komt van React? Dit is Vue's tegenhanger van useEffect(() => {...}, []). Zelfde idee, andere naam.
onMounted — de belangrijkste
onMounted draait één keer, vlak nadat het component op het scherm verschijnt. Verreweg de meest gebruikte hook.
import { onMounted } from 'vue'
Basis Voorbeeld
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log('Component is gemount!')
})
</script>
Data ophalen bij laden (het klassieke gebruik)
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
onMounted(async () => {
const res = await fetch('https://api.example.com/users')
users.value = await res.json()
loading.value = false
})
</script>
<template>
<p v-if="loading">Laden...</p>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Patroon om te onthouden: Data ophalen bij het laden van een pagina? Doe het in onMounted. Dat is het.
onUnmounted — opruimen
Heb je in onMounted een timer gestart of een listener toegevoegd? Dan moet je dat in onUnmounted weer stoppen. Anders blijft die doorrennen, ook nadat de gebruiker naar een andere pagina is.
Timer voorbeeld
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const seconds = ref(0)
let interval = null
onMounted(() => {
interval = setInterval(() => {
seconds.value++
}, 1000)
})
onUnmounted(() => {
clearInterval(interval)
})
</script>
<template>
<p>Seconden: {{ seconds }}</p>
</template>
Belangrijk: Bij elke setInterval of window.addEventListener hoort een cleanup in onUnmounted. Vergeet je dat? Dan krijg je rare bugs en memory leaks.
Praktische Voorbeelden
1. Focus op input bij laden
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
<template>
<input ref="inputRef" />
</template>
2. API-call met loading & error
<script setup>
import { ref, onMounted } from 'vue'
const data = ref(null)
const loading = ref(true)
const error = ref(null)
onMounted(async () => {
try {
const res = await fetch('https://api.example.com/data')
if (!res.ok) throw new Error('Server error')
data.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
</script>
<template>
<p v-if="loading">Laden...</p>
<p v-else-if="error">{{ error }}</p>
<pre v-else>{{ data }}</pre>
</template>
Belangrijke Regels
- Lifecycle hooks staan op top-level van
<script setup>— niet binnen functies of condities - Data ophalen? →
onMounted - Timer/listener gestart? → bijbehorende
onUnmountedniet vergeten - DOM ref gebruiken? → wacht op
onMounted, anders bestaat het element nog niet
Veelgemaakte Fouten
Fout — timer niet opruimen:
onMounted(() => {
setInterval(() => console.log('tick'), 1000)
// ❌ Geen cleanup = blijft draaien na unmount!
})
Goed:
let id
onMounted(() => {
id = setInterval(() => console.log('tick'), 1000)
})
onUnmounted(() => clearInterval(id)) // ✅
Fout — DOM ref te vroeg gebruiken:
<script setup>
import { ref } from 'vue'
const myInput = ref(null)
myInput.value.focus() // ❌ template bestaat nog niet
</script>
Goed:
<script setup>
import { ref, onMounted } from 'vue'
const myInput = ref(null)
onMounted(() => myInput.value.focus()) // ✅
</script>