Lifecycle Hooks
Den Lebenszyklus einer Vue-Komponente verstehen und gezielt nutzen
Was sind Lifecycle Hooks?
Jede Vue-Komponente durchläuft einen klar definierten Lebenszyklus: Sie wird erstellt, ins DOM eingehängt (mounted), bei Zustandsänderungen aktualisiert und schließlich wieder ausgehängt (unmounted). Lifecycle Hooks sind Funktionen, mit denen du Code zu genau diesen Zeitpunkten ausführen kannst.
Stell dir das wie Callbacks vor, die Vue automatisch aufruft, wenn eine Komponente eine bestimmte Phase erreicht. Du registrierst sie innerhalb von <script setup> und Vue kümmert sich um den Rest.
Lifecycle Hooks auf einen Blick
Die wichtigsten Hooks in der Composition API sind: onMounted, onUpdated und onUnmounted. Für die meisten Anwendungsfälle brauchst du nur diese drei. Die „Before"-Varianten (onBeforeMount, onBeforeUpdate, onBeforeUnmount) sind seltener nötig, aber gut zu kennen.
Der Lifecycle im Überblick
Der vollständige Lebenszyklus einer Vue-Komponente lässt sich als Abfolge von Phasen und Hooks darstellen:
── Komponente wird erstellt ──
📦 setup() ← hier läuft dein <script setup>-Code
── DOM wird vorbereitet ──
⏳ onBeforeMount ← DOM existiert noch nicht
✅ onMounted ← DOM ist verfügbar!
── Reaktive Daten ändern sich ──
⏳ onBeforeUpdate ← DOM zeigt noch alten Zustand
🔄 onUpdated ← DOM ist aktualisiert
── Komponente wird entfernt ──
⏳ onBeforeUnmount ← DOM ist noch da
💀 onUnmounted ← Komponente ist weg, aufräumen!
Beachte: setup() ist kein Hook, den du registrierst — er ist der Kontext, in dem du die Hooks registrierst. Dein gesamter <script setup>-Code läuft in der setup-Phase.
<script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 1. setup() — läuft jetzt, synchron
console.log('setup: Komponente wird initialisiert')
onBeforeMount(() => {
console.log('onBeforeMount: DOM wird gleich erstellt')
})
onMounted(() => {
console.log('onMounted: DOM ist verfügbar! 🎉')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate: State hat sich geändert, DOM wird gleich aktualisiert')
})
onUpdated(() => {
console.log('onUpdated: DOM wurde aktualisiert')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount: Komponente wird gleich entfernt')
})
onUnmounted(() => {
console.log('onUnmounted: Komponente entfernt, Tschüss! 👋')
})
</script>onMounted
onMounted ist der mit Abstand am häufigsten verwendete Lifecycle Hook. Er wird aufgerufen, sobald die Komponente ins DOM eingehängt wurde. Das bedeutet: Du hast Zugriff auf DOM-Elemente, kannst Daten laden oder Drittanbieter-Bibliotheken initialisieren.
Typische Anwendungsfälle
- Daten von einer API laden
- DOM-Elemente messen (Breite, Höhe, Position)
- Drittanbieter-Bibliotheken initialisieren (z.B. Charts, Maps)
- Event Listener auf
windowoderdocumentregistrieren
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(true)
const error = ref(null)
onMounted(async () => {
try {
const response = await fetch('/api/users')
users.value = await response.json()
} catch (err) {
error.value = 'Benutzer konnten nicht geladen werden'
console.error(err)
} finally {
loading.value = false
}
})
</script>
<template>
<div v-if="loading" class="spinner">Lade Benutzer…</div>
<div v-else-if="error" class="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>Nuxt-Tipp: useFetch statt manuellem fetch
In Nuxt bevorzuge useFetch() oder useAsyncData() statt manuellem fetch in onMounted. Diese Composables handhaben SSR, Caching und automatische Reaktivität für dich. Du sparst dir Loading-States, Error-Handling und Race Conditions.
onUpdated
onUpdated wird aufgerufen, nachdem eine reaktive Zustandsänderung das DOM aktualisiert hat. Das ist nützlich, wenn du nach einem Re-Render auf das aktualisierte DOM zugreifen musst — zum Beispiel um Scroll-Positionen anzupassen oder DOM-Messungen durchzuführen.
<script setup>
import { ref, onUpdated } from 'vue'
const messages = ref([])
const chatContainer = ref(null)
// Nach jedem DOM-Update: ans Ende scrollen
onUpdated(() => {
if (chatContainer.value) {
chatContainer.value.scrollTop = chatContainer.value.scrollHeight
}
})
function addMessage(text) {
messages.value.push({
id: Date.now(),
text,
time: new Date().toLocaleTimeString('de-DE')
})
}
</script>
<template>
<div ref="chatContainer" class="chat-container">
<div v-for="msg in messages" :key="msg.id" class="message">
<span class="time">{{ msg.time }}</span>
<p>{{ msg.text }}</p>
</div>
</div>
</template>Vorsicht: Endlosschleifen!
Ändere niemals reaktiven State direkt in onUpdated! Das löst einen neuen Render aus, der wiederum onUpdated auslöst — eine Endlosschleife. Wenn du State basierend auf DOM-Änderungen setzen musst, verwende nextTick() oder prüfe mit einer Bedingung, ob die Änderung wirklich nötig ist.
onUnmounted
onUnmounted ist dein Aufräum-Hook. Hier entfernst du alles, was du in onMounted eingerichtet hast: Event Listener, Intervalle, Timeouts, WebSocket-Verbindungen, Intersection Observers. Vergisst du das Aufräumen, hast du Memory Leaks.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const currentTime = ref('')
let intervalId = null // kein ref — wird nicht im Template gebraucht
onMounted(() => {
// Intervall starten: Uhrzeit jede Sekunde aktualisieren
intervalId = setInterval(() => {
currentTime.value = new Date().toLocaleTimeString('de-DE')
}, 1000)
// Sofort einmal setzen
currentTime.value = new Date().toLocaleTimeString('de-DE')
})
onUnmounted(() => {
// Intervall aufräumen — sonst Memory Leak!
if (intervalId) {
clearInterval(intervalId)
intervalId = null
}
})
</script>
<template>
<div class="live-clock">
🕐 {{ currentTime }}
</div>
</template> Beachte das Muster: Was in onMounted aufgebaut wird, wird in onUnmounted wieder abgebaut. Dieses Setup/Cleanup-Pattern ist eines der wichtigsten Konzepte im Umgang mit Lifecycle Hooks.
Die „Before"-Hooks
Neben den Haupt-Hooks gibt es jeweils eine „Before"-Variante: onBeforeMount, onBeforeUpdate und onBeforeUnmount. Sie laufen bevor Vue das DOM aktualisiert — du hast also noch Zugriff auf den vorherigen Zustand.
Wann brauchst du sie?
- onBeforeUpdate: Snapshot des DOM-Zustands vor einem Update (z.B. Scroll-Position merken)
- onBeforeUnmount: Letzte Aktionen vor dem Entfernen, wenn du noch DOM-Zugriff brauchst
- onBeforeMount: Selten benötigt —
setup()reicht meistens aus
<script setup>
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const items = ref(['Eintrag 1', 'Eintrag 2'])
const listRef = ref(null)
let previousScrollHeight = 0
// Vor dem Update: aktuellen Scroll-Zustand merken
onBeforeUpdate(() => {
if (listRef.value) {
previousScrollHeight = listRef.value.scrollHeight
}
})
// Nach dem Update: Scroll-Position anpassen
onUpdated(() => {
if (listRef.value) {
const newScrollHeight = listRef.value.scrollHeight
const heightDiff = newScrollHeight - previousScrollHeight
listRef.value.scrollTop += heightDiff
}
})
function addItem() {
items.value.unshift(`Eintrag ${items.value.length + 1}`)
}
</script>Häufige Patterns
In der Praxis wirst du immer wieder auf die gleichen Lifecycle-Muster stoßen. Hier sind die wichtigsten:
1. Fetch on Mount mit Loading State
Das Standardmuster für Datenladung: Zeige einen Ladezustand, lade Daten in onMounted, und aktualisiere den State.
<script setup>
import { ref, onMounted } from 'vue'
// State
const data = ref(null)
const loading = ref(true)
const error = ref(null)
// Daten laden, sobald die Komponente eingehängt ist
onMounted(async () => {
try {
const res = await fetch('https://api.example.com/articles')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
data.value = await res.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
})
</script>
<template>
<div v-if="loading">⏳ Wird geladen…</div>
<div v-else-if="error" class="text-red-500">
❌ Fehler: {{ error }}
</div>
<article v-else v-for="article in data" :key="article.id">
<h2>{{ article.title }}</h2>
<p>{{ article.excerpt }}</p>
</article>
</template>2. Setup + Cleanup (Event Listener)
Immer wenn du in onMounted etwas registrierst, räume es in onUnmounted auf. Das gilt für Event Listener, WebSockets, Intervalle und Observer.
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const windowWidth = ref(window.innerWidth)
function handleResize() {
windowWidth.value = window.innerWidth
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
</script>
<template>
<p>Fensterbreite: {{ windowWidth }}px</p>
</template>3. Scroll-Position wiederherstellen
Nutze onBeforeUpdate, um die Scroll-Position vor einem Update zu speichern, und onUpdated, um sie wiederherzustellen:
<script setup>
import { ref, onBeforeUpdate, onUpdated } from 'vue'
const messages = ref([])
const container = ref(null)
let savedScrollTop = 0
let savedScrollHeight = 0
onBeforeUpdate(() => {
if (container.value) {
savedScrollTop = container.value.scrollTop
savedScrollHeight = container.value.scrollHeight
}
})
onUpdated(() => {
if (container.value) {
// Neue Nachrichten oben eingefügt?
// → Scroll-Position korrigieren, damit der Inhalt nicht springt
const heightDiff = container.value.scrollHeight - savedScrollHeight
container.value.scrollTop = savedScrollTop + heightDiff
}
})
</script>Rails-Vergleich
Als Rails-Entwickler kennst du Callbacks aus zwei Kontexten: Controller-Callbacks wie before_action und ActiveRecord-Callbacks wie before_save. Vue Lifecycle Hooks funktionieren nach einem ähnlichen Prinzip — aber in einem völlig anderen Kontext.
Rails Callbacks vs. Vue Lifecycle Hooks
Gemeinsamkeiten: Beide ermöglichen dir, Code zu bestimmten Zeitpunkten im Lebenszyklus eines Objekts auszuführen. Das Muster „vorher → Aktion → nachher" existiert in beiden Welten.
| Rails | Vue | Anmerkung |
|---|---|---|
before_action | onBeforeMount | Setup vor der Hauptaktion |
after_action | onMounted | Aktion abgeschlossen, Ergebnis verfügbar |
after_save | onUpdated | Zustand hat sich geändert und ist persistiert |
before_destroy | onBeforeUnmount | Letzte Chance vor dem Aufräumen |
Wichtiger Unterschied: Rails-Callbacks sind serverseitig und an einen HTTP-Request oder Datenbankoperation gebunden. Vue Hooks sind clientseitig und an den Lebenszyklus einer UI-Komponente im Browser gebunden. Ein Rails-Controller wird pro Request erstellt und verworfen — eine Vue-Komponente lebt so lange, wie sie im DOM eingehängt ist.
Interaktive Demo
In dieser Demo siehst du die Lifecycle Hooks in Aktion. Klicke auf die Buttons und beobachte, welche Hooks in welcher Reihenfolge ausgelöst werden.
// Lifecycle-Log:
Noch keine Events. Klicke „Komponente einhängen" zum Starten.
Hinweis zur Demo
In einer echten Anwendung wäre die Kind-Komponente eine eigene .vue-Datei mit eigenen Lifecycle Hooks. Hier simulieren wir das Verhalten innerhalb einer Seite, damit du das Prinzip direkt sehen kannst, ohne zwischen Dateien wechseln zu müssen.