Lifecycle Hooks
Jede Vue-Komponente durchläuft einen Lebenszyklus: Erstellung, Mounting, Updates und Zerstörung. Lifecycle Hooks ermöglichen es, Code zu bestimmten Zeitpunkten dieses Zyklus auszuführen.
Lifecycle Diagramm
Abschnitt betitelt „Lifecycle Diagramm“┌─────────────────────────────────────────────────────────────────┐│ CREATION PHASE │├─────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────┐ ││ │ beforeCreate │ ← Options API: data/methods nicht ││ │ (Options only) │ verfügbar ││ └────────┬────────┘ ││ ▼ ││ ┌─────────────────┐ ││ │ created │ ← Reaktive Daten verfügbar ││ │ / setup() │ DOM noch NICHT verfügbar ││ └────────┬────────┘ API-Calls hier möglich ││ │ │└────────────┼────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ MOUNTING PHASE │├─────────────────────────────────────────────────────────────────┤│ │ ││ ┌────────┴────────┐ ││ │ beforeMount │ ← Template kompiliert ││ │ onBeforeMount() │ DOM noch nicht eingefügt ││ └────────┬────────┘ ││ ▼ ││ ┌─────────────────┐ ││ │ mounted │ ← Komponente im DOM! 🎉 ││ │ onMounted() │ Template Refs verfügbar ││ └────────┬────────┘ DOM-Zugriff möglich ││ │ │└────────────┼────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ UPDATE PHASE ││ (bei reaktiven Änderungen) │├─────────────────────────────────────────────────────────────────┤│ │ ││ ┌────────┴────────┐ ││ │ beforeUpdate │ ← Daten geändert ││ │ onBeforeUpdate()│ DOM noch im alten Zustand ││ └────────┬────────┘ ││ ▼ ││ ┌─────────────────┐ ││ │ updated │ ← DOM wurde aktualisiert ││ │ onUpdated() │ Vorsicht: keine State-Änderungen! ││ └────────┬────────┘ ││ │ ││ ▼ ││ ┌───────┐ ││ │ Loop │ ←─── weitere reaktive Änderungen ││ └───┬───┘ ││ │ │└────────────┼────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────┐│ UNMOUNT PHASE ││ (Komponente wird entfernt) │├─────────────────────────────────────────────────────────────────┤│ │ ││ ┌────────┴────────┐ ││ │ beforeUnmount │ ← Komponente noch funktional ││ │onBeforeUnmount()│ Letzte Chance für Cleanup ││ └────────┬────────┘ ││ ▼ ││ ┌─────────────────┐ ││ │ unmounted │ ← Komponente entfernt ││ │ onUnmounted() │ Event Listener aufräumen! ││ └─────────────────┘ Timer/Subscriptions beenden ││ │└─────────────────────────────────────────────────────────────────┘Übersicht: Alle Hooks
Abschnitt betitelt „Übersicht: Alle Hooks“| Phase | Options API | Composition API | Wann verwenden |
|---|---|---|---|
| Creation | beforeCreate | - | Selten (Options API) |
created | setup() | API-Calls, Initialisierung | |
| Mounting | beforeMount | onBeforeMount() | Selten |
mounted | onMounted() | DOM-Zugriff, Libraries | |
| Updating | beforeUpdate | onBeforeUpdate() | DOM vor Update lesen |
updated | onUpdated() | Nach DOM-Update (Vorsicht!) | |
| Unmounting | beforeUnmount | onBeforeUnmount() | Cleanup starten |
unmounted | onUnmounted() | Cleanup abschließen | |
| Special | errorCaptured | onErrorCaptured() | Error Boundaries |
activated | onActivated() | KeepAlive aktiviert | |
deactivated | onDeactivated() | KeepAlive deaktiviert |
Hook-Beispiele im Detail
Abschnitt betitelt „Hook-Beispiele im Detail“setup() / created - Initialisierung
Abschnitt betitelt „setup() / created - Initialisierung“Der <script setup> Code entspricht der Creation-Phase. Hier werden reaktive Daten erstellt und API-Calls gestartet.
<script setup>import { ref } from 'vue'
// ═══════════════════════════════════════════════════════════// Alles hier läuft während der CREATION Phase// ═══════════════════════════════════════════════════════════
// 1. Reaktive Daten initialisierenconst users = ref([])const isLoading = ref(true)const error = ref(null)
// 2. API-Calls starten (ohne await - non-blocking)fetch('/api/users') .then(response => response.json()) .then(data => { users.value = data }) .catch(err => { error.value = err.message }) .finally(() => { isLoading.value = false })
// 3. Computed/Watchers einrichten// ... (siehe andere Kapitel)
console.log('1. Setup/Created läuft')</script>Verwende setup() für:
- Reaktive Variablen erstellen (
ref,reactive) - API-Calls starten
- Computed Properties definieren
- Watchers einrichten
onMounted() - DOM ist bereit
Abschnitt betitelt „onMounted() - DOM ist bereit“Der wichtigste Hook für DOM-Interaktion. Die Komponente ist im DOM und Template Refs sind verfügbar.
<script setup>import { ref, onMounted } from 'vue'
const inputRef = ref(null)const containerRef = ref(null)
onMounted(() => { console.log('2. Mounted - DOM verfügbar!')
// Input fokussieren inputRef.value?.focus()
// Element-Größe messen const { width, height } = containerRef.value.getBoundingClientRect() console.log(`Container: ${width}x${height}`)
// CSS-Klasse hinzufügen containerRef.value.classList.add('is-mounted')})</script>
<template> <div ref="containerRef"> <input ref="inputRef" placeholder="Automatisch fokussiert" /> </div></template><script setup>import { ref, onMounted, onUnmounted } from 'vue'import Chart from 'chart.js/auto'
const canvasRef = ref(null)let chartInstance = null
onMounted(() => { // Chart.js initialisieren chartInstance = new Chart(canvasRef.value, { type: 'bar', data: { labels: ['Jan', 'Feb', 'Mar', 'Apr'], datasets: [{ label: 'Verkäufe', data: [12, 19, 3, 5], backgroundColor: '#3b82f6' }] } })})
onUnmounted(() => { // Wichtig: Chart zerstören! chartInstance?.destroy()})</script>
<template> <canvas ref="canvasRef"></canvas></template><script setup>import { ref, onMounted, onUnmounted } from 'vue'
const windowWidth = ref(window.innerWidth)const scrollY = ref(0)
// Handler als benannte Funktionen (für removeEventListener)function handleResize() { windowWidth.value = window.innerWidth}
function handleScroll() { scrollY.value = window.scrollY}
onMounted(() => { window.addEventListener('resize', handleResize) window.addEventListener('scroll', handleScroll, { passive: true })})
onUnmounted(() => { // IMMER aufräumen! window.removeEventListener('resize', handleResize) window.removeEventListener('scroll', handleScroll)})</script>
<template> <div> <p>Fensterbreite: {{ windowWidth }}px</p> <p>Scroll-Position: {{ scrollY }}px</p> </div></template><script setup>import { ref, onMounted } from 'vue'
const userData = ref(null)const isLoading = ref(true)
// Async-Funktion in onMountedonMounted(async () => { try { const response = await fetch('/api/user/profile') userData.value = await response.json() } catch (error) { console.error('Fehler:', error) } finally { isLoading.value = false }})</script>
<template> <div v-if="isLoading">Laden...</div> <div v-else-if="userData"> <h2>{{ userData.name }}</h2> <p>{{ userData.email }}</p> </div></template>Verwende onMounted() für:
- Template Refs zugreifen
- Externe Libraries initialisieren (Chart.js, Maps, etc.)
- Window/Document Event Listener
- Element-Größen messen
- Animationen starten
- Focus setzen
onUnmounted() - Cleanup
Abschnitt betitelt „onUnmounted() - Cleanup“Kritisch für die Vermeidung von Memory Leaks! Alles was in onMounted registriert wird, muss hier aufgeräumt werden.
<script setup>import { ref, onMounted, onUnmounted } from 'vue'
// Ressourcen die aufgeräumt werden müssenlet intervalId = nulllet websocket = nulllet observer = nullconst abortController = new AbortController()
onMounted(() => { // 1. Timer starten intervalId = setInterval(() => { console.log('Tick') }, 1000)
// 2. WebSocket öffnen websocket = new WebSocket('wss://api.example.com') websocket.onmessage = (event) => { console.log('Nachricht:', event.data) }
// 3. IntersectionObserver observer = new IntersectionObserver((entries) => { entries.forEach(entry => { console.log('Sichtbar:', entry.isIntersecting) }) }) observer.observe(document.querySelector('.target'))
// 4. Fetch mit AbortController fetch('/api/data', { signal: abortController.signal }) .then(r => r.json()) .then(data => console.log(data))})
onUnmounted(() => { // ═══════════════════════════════════════════════════════════ // ALLES AUFRÄUMEN! // ═══════════════════════════════════════════════════════════
// 1. Timer stoppen if (intervalId) { clearInterval(intervalId) }
// 2. WebSocket schließen if (websocket) { websocket.close() }
// 3. Observer trennen if (observer) { observer.disconnect() }
// 4. Laufende Requests abbrechen abortController.abort()
console.log('Alle Ressourcen aufgeräumt!')})</script>onBeforeUpdate() & onUpdated() - DOM Updates
Abschnitt betitelt „onBeforeUpdate() & onUpdated() - DOM Updates“Diese Hooks werden bei jeder reaktiven Änderung ausgelöst, die das DOM betrifft.
<script setup>import { ref, onBeforeUpdate, onUpdated } from 'vue'
const items = ref(['Apfel', 'Banane', 'Kirsche'])const listRef = ref(null)
onBeforeUpdate(() => { // DOM hat noch den ALTEN Zustand console.log('Vor Update:') console.log('- Kinder:', listRef.value?.children.length)})
onUpdated(() => { // DOM hat den NEUEN Zustand console.log('Nach Update:') console.log('- Kinder:', listRef.value?.children.length)
// ⚠️ VORSICHT: State-Änderungen hier können // Endlosschleifen verursachen! // items.value.push('neu') // ← NICHT TUN!})
function addItem() { items.value.push(`Item ${items.value.length + 1}`)}</script>
<template> <ul ref="listRef"> <li v-for="item in items" :key="item">{{ item }}</li> </ul> <button @click="addItem">Hinzufügen</button></template>Spezielle Hooks
Abschnitt betitelt „Spezielle Hooks“onErrorCaptured() - Error Boundary
Abschnitt betitelt „onErrorCaptured() - Error Boundary“Fängt Fehler von Kind-Komponenten ab:
<script setup>import { ref, onErrorCaptured } from 'vue'
const error = ref(null)const errorInfo = ref(null)
onErrorCaptured((err, instance, info) => { // err: Der Fehler // instance: Die Komponente die den Fehler verursachte // info: Wo der Fehler auftrat (render, watcher, etc.)
console.error('Fehler gefangen:', err) console.log('Info:', info)
error.value = err errorInfo.value = info
// return false = Fehler nicht weiter nach oben propagieren return false})
function resetError() { error.value = null errorInfo.value = null}</script>
<template> <div v-if="error" class="error-boundary"> <h3>Etwas ist schiefgelaufen</h3> <p>{{ error.message }}</p> <p><small>Ort: {{ errorInfo }}</small></p> <button @click="resetError">Erneut versuchen</button> </div>
<slot v-else /></template>
<style scoped>.error-boundary { padding: 1rem; background: #fee; border: 1px solid #f00; border-radius: 4px;}</style>onActivated() & onDeactivated() - KeepAlive
Abschnitt betitelt „onActivated() & onDeactivated() - KeepAlive“Für Komponenten die mit <KeepAlive> gecacht werden:
<script setup>import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
const data = ref(null)let pollInterval = null
async function fetchData() { const response = await fetch('/api/data') data.value = await response.json()}
function startPolling() { pollInterval = setInterval(fetchData, 30000)}
function stopPolling() { if (pollInterval) { clearInterval(pollInterval) pollInterval = null }}
// Normale Lifecycle Hooks (nur beim ersten Mal)onMounted(() => { console.log('Komponente erstellt') fetchData() startPolling()})
onUnmounted(() => { console.log('Komponente zerstört') stopPolling()})
// KeepAlive Hooks (bei jedem Aktivieren/Deaktivieren)onActivated(() => { console.log('Wieder aktiv!') fetchData() // Daten aktualisieren startPolling() // Polling wieder starten})
onDeactivated(() => { console.log('Deaktiviert (gecacht)') stopPolling() // Polling pausieren})</script><script setup>import { ref } from 'vue'import TabContent from './TabContent.vue'
const currentTab = ref('home')</script>
<template> <nav> <button @click="currentTab = 'home'">Home</button> <button @click="currentTab = 'profile'">Profil</button> </nav>
<!-- KeepAlive cacht Komponenten statt sie zu zerstören --> <KeepAlive> <component :is="currentTab === 'home' ? HomeTab : ProfileTab" /> </KeepAlive></template>Ausführungsreihenfolge
Abschnitt betitelt „Ausführungsreihenfolge“Einzelne Komponente
Abschnitt betitelt „Einzelne Komponente“┌─────────────────────────────────────────┐│ 1. setup() / beforeCreate / created ││ 2. onBeforeMount() ││ 3. onMounted() ← DOM bereit ││ ↓ ││ 4. [Reaktive Änderung] ││ 5. onBeforeUpdate() ││ 6. onUpdated() ││ ↓ (wiederholt bei Änderungen) ││ ││ 7. onBeforeUnmount() ← Cleanup Start ││ 8. onUnmounted() ← Cleanup Ende │└─────────────────────────────────────────┘Verschachtelte Komponenten
Abschnitt betitelt „Verschachtelte Komponenten“Bei Parent/Child-Beziehungen ist die Reihenfolge wichtig:
MOUNTING:─────────────────────────────────────────Parent setup()Parent onBeforeMount()│├── Child setup()├── Child onBeforeMount()├── Child onMounted() ← Kind zuerst fertig!│Parent onMounted() ← Eltern zuletzt
UNMOUNTING:─────────────────────────────────────────Parent onBeforeUnmount()│├── Child onBeforeUnmount()├── Child onUnmounted() ← Kind zuerst weg│Parent onUnmounted() ← Eltern zuletztDemo zum Testen:
<script setup>import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from 'vue'import Child from './Child.vue'
onBeforeMount(() => console.log('Parent: beforeMount'))onMounted(() => console.log('Parent: mounted'))onBeforeUnmount(() => console.log('Parent: beforeUnmount'))onUnmounted(() => console.log('Parent: unmounted'))</script>
<template> <div> <h2>Parent</h2> <Child /> </div></template><script setup>import { onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
onBeforeMount(() => console.log(' Child: beforeMount'))onMounted(() => console.log(' Child: mounted'))onBeforeUnmount(() => console.log(' Child: beforeUnmount'))onUnmounted(() => console.log(' Child: unmounted'))</script>
<template> <p>Child Komponente</p></template>Praktisches Beispiel: Komplette Komponente
Abschnitt betitelt „Praktisches Beispiel: Komplette Komponente“<script setup>import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
// Propsconst props = defineProps({ userId: { type: Number, required: true }})
// Stateconst user = ref(null)const isLoading = ref(true)const error = ref(null)const isOnline = ref(navigator.onLine)
// Computedconst displayName = computed(() => user.value?.name ?? 'Unbekannt')
// API Funktionasync function fetchUser(id) { isLoading.value = true error.value = null
try { const response = await fetch(`/api/users/${id}`) if (!response.ok) throw new Error('User nicht gefunden') user.value = await response.json() } catch (e) { error.value = e.message } finally { isLoading.value = false }}
// Event Handlerfunction handleOnline() { isOnline.value = true fetchUser(props.userId) // Daten neu laden}
function handleOffline() { isOnline.value = false}
// Bei Props-Änderung neu ladenwatch(() => props.userId, (newId) => { fetchUser(newId)})
// LifecycleonMounted(() => { // Initial laden fetchUser(props.userId)
// Online-Status überwachen window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline)})
onUnmounted(() => { // Cleanup window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline)})</script>
<template> <div class="user-profile"> <div v-if="!isOnline" class="offline-banner"> Offline - Daten möglicherweise veraltet </div>
<div v-if="isLoading" class="loading"> Laden... </div>
<div v-else-if="error" class="error"> Fehler: {{ error }} </div>
<div v-else-if="user" class="content"> <h2>{{ displayName }}</h2> <p>{{ user.email }}</p> </div> </div></template>Best Practices
Abschnitt betitelt „Best Practices“onMountedfür DOM-Zugriff und externe LibrariesonUnmountedfür Cleanup (Timer, Listener, Connections)- API-Calls in
setup()oderonMounted - Benannte Funktionen für Event Handler (für
removeEventListener) KeepAlive-Hooks für gecachte Komponenten
- State ändern in
onUpdated→ Endlosschleifen! - DOM-Zugriff vor
onMounted→ Element existiert nicht - Cleanup vergessen → Memory Leaks!
- Zu viel Logik in Hooks → extrahiere in Composables
- Anonyme Handler bei
addEventListener→ kann nicht entfernt werden
Dokumentation
Abschnitt betitelt „Dokumentation“Offizielle Vue Dokumentation:
- Lifecycle Hooks - Übersicht
- Lifecycle Diagram - Offizielles Diagramm
API Referenz:
Weiterführend:
- Template Refs - DOM-Zugriff
- Composables - Lifecycle-Logik extrahieren