Zum Inhalt springen

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.

┌─────────────────────────────────────────────────────────────────┐
│ 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 │
│ │
└─────────────────────────────────────────────────────────────────┘
PhaseOptions APIComposition APIWann verwenden
CreationbeforeCreate-Selten (Options API)
createdsetup()API-Calls, Initialisierung
MountingbeforeMountonBeforeMount()Selten
mountedonMounted()DOM-Zugriff, Libraries
UpdatingbeforeUpdateonBeforeUpdate()DOM vor Update lesen
updatedonUpdated()Nach DOM-Update (Vorsicht!)
UnmountingbeforeUnmountonBeforeUnmount()Cleanup starten
unmountedonUnmounted()Cleanup abschließen
SpecialerrorCapturedonErrorCaptured()Error Boundaries
activatedonActivated()KeepAlive aktiviert
deactivatedonDeactivated()KeepAlive deaktiviert

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 initialisieren
const 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

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>

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

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üssen
let intervalId = null
let websocket = null
let observer = null
const 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>

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>

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>

Für Komponenten die mit <KeepAlive> gecacht werden:

TabContent.vue
<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>
Parent.vue
<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>
┌─────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────┘

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 zuletzt

Demo zum Testen:

Parent.vue
<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>
Child.vue
<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>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
// Props
const props = defineProps({
userId: {
type: Number,
required: true
}
})
// State
const user = ref(null)
const isLoading = ref(true)
const error = ref(null)
const isOnline = ref(navigator.onLine)
// Computed
const displayName = computed(() =>
user.value?.name ?? 'Unbekannt'
)
// API Funktion
async 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 Handler
function handleOnline() {
isOnline.value = true
fetchUser(props.userId) // Daten neu laden
}
function handleOffline() {
isOnline.value = false
}
// Bei Props-Änderung neu laden
watch(() => props.userId, (newId) => {
fetchUser(newId)
})
// Lifecycle
onMounted(() => {
// 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>
  • onMounted für DOM-Zugriff und externe Libraries
  • onUnmounted für Cleanup (Timer, Listener, Connections)
  • API-Calls in setup() oder onMounted
  • 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

Offizielle Vue Dokumentation:

API Referenz:

Weiterführend: