Appearance
Kapitel 39: Vue 3 Fortgeschrittene Konzepte
📙 Lernziel: Benutzerdefinierte Direktiven, Teleport, Suspense, dynamische Komponenten meistern!
39.1 Benutzerdefinierte Direktiven
Was sind benutzerdefinierte Direktiven? Eigene Direktiven können als wiederverwendbare Funktionen erstellt werden.
Globale Direktive (main.js):
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// Globale Direktive registrieren
app.directive('fokus', {
mounted(el) {
el.focus()
}
})
app.mount('#app')Lokale Direktive (in Komponente):
vue
<script setup>
// Lokale Direktive (Composition API)
const vFokus = {
mounted(el) {
el.focus()
}
}
// Oder als Funktion (Shortand)
const vHighlight = (el, binding) => {
el.style.backgroundColor = binding.value || 'yellow'
}
</script>
<template>
<div>
<input v-fokus placeholder="Autofokus" />
<p v-highlight="'lightblue'">Hervorgehoben</p>
</div>
</template>Hook-Funktionen für Direktiven:
beforeMount: Bevor Element eingehängt wirdmounted: Nachdem Element eingehängt wurdebeforeUpdate: Bevor Element aktualisiert wirdupdated: Nachdem Element aktualisiert wurdebeforeUnmount: Bevor Element entfernt wirdunmounted: Nachdem Element entfernt wurde
Beispiel (erweiterte Direktive):
vue
<script setup>
const vPotion = {
mounted(el, binding) {
el.style.position = 'fixed'
el.style.top = binding.value.top + 'px'
el.style.left = binding.value.left + 'px'
},
updated(el, binding) {
el.style.top = binding.value.top + 'px'
el.style.left = binding.value.left + 'px'
}
}
</script>
<template>
<div v-potion="{ top: 100, left: 50 }">
Ich bin fixiert!
</div>
</template>39.2 Teleport
Was ist Teleport? Teleport ermöglicht es, Elemente in einen anderen Bereich des DOMs zu "teleportieren".
Anwendungsfall:
- Modal-Dialoge
- Tooltips
- Benachrichtigungen
Beispiel (Modal):
vue
<script setup>
import { ref } from 'vue'
const isModalOpen = ref(false)
const openModal = () => {
isModalOpen.value = true
}
const closeModal = () => {
isModalOpen.value = false
}
</script>
<template>
<div>
<button @click="openModal">Modal öffnen</button>
<!-- Teleport to body -->
<teleport to="body">
<div v-if="isModalOpen" class="modal">
<div class="modal-content">
<h2>Modal Titel</h2>
<p>Dies ist ein Modal-Fenster.</p>
<button @click="closeModal">Schließen</button>
</div>
</div>
</teleport>
</div>
</template>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
max-width: 500px;
width: 100%;
}
</style>Wichtig:
- ✅
toAttribut kann CSS-Selektor sein (z.B.body,#modal-container) - ✅ Element wird im DOM verschoben, bleibt aber logisch in der Komponente
39.3 Suspense
Was ist Suspense? Suspense ermöglicht das Anzeigen von Ladezuständen während asynchroner Komponenten geladen werden.
Beispiel (Async Komponente):
vue
<!-- AsyncComponent.vue -->
<script setup>
// Simuliere API-Aufruf
const data = await new Promise(resolve => {
setTimeout(() => {
resolve({ message: 'Daten geladen!' })
}, 2000)
})
</script>
<template>
<div>
<h2>{{ data.message }}</h2>
</div>
</template>Verwendung mit Suspense:
vue
<script setup>
import { defineAsyncComponent } from 'vue'
// Asynchrone Komponente laden
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
<template>
<div>
<suspense>
<!-- Standard-Inhalt (während Laden) -->
<template #default>
<AsyncComponent />
</template>
<!-- Ladeanzeige -->
<template #fallback>
<div class="loading">Lädt...</div>
</template>
</suspense>
</div>
</template>
<style scoped>
.loading {
text-align: center;
padding: 50px;
font-size: 1.2em;
color: #666;
}
</style>Wichtig:
- ✅
#defaultSlot: Die asynchrone Komponente - ✅
#fallbackSlot: Ladeanzeige - ✅ Mehrere asynchrone Komponenten können gleichzeitig geladen werden
39.4 Dynamische Komponenten & keep-alive
Dynamische Komponenten:
vue
<script setup>
import { ref, shallowRef } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = shallowRef(ComponentA)
const switchComponent = () => {
currentComponent.value = currentComponent.value === ComponentA
? ComponentB
: ComponentA
}
</script>
<template>
<div>
<button @click="switchComponent">Komponente wechseln</button>
<!-- Dynamische Komponente -->
<component :is="currentComponent" />
</div>
</template>keep-alive (Zustand erhalten):
vue
<script setup>
import { ref, shallowRef } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
const currentComponent = shallowRef(ComponentA)
</script>
<template>
<div>
<button @click="currentComponent = ComponentA">A</button>
<button @click="currentComponent = ComponentB">B</button>
<!-- Keep Alive: Zustand wird beim Wechsel erhalten -->
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</div>
</template>Lebenszyklus-Hooks für keep-alive:
onActivated(): Wenn Komponente aktiviert wirdonDeactivated(): Wenn Komponente deaktiviert wird
Beispiel:
vue
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'
const count = ref(0)
onActivated(() => {
console.log('Komponente aktiviert')
})
onDeactivated(() => {
console.log('Komponente deaktiviert')
})
</script>39.5 Modulare Entwicklung & Komponenten-Struktur
Best Practices für Komponenten-Struktur:
1. Einzelne Dateiverantwortung
src/
├── components/ # Wiederverwendbare Komponenten
│ ├── BaseButton.vue
│ ├── BaseInput.vue
│ └── BaseModal.vue
├── views/ # Seiten-Komponenten (Routen)
│ ├── HomeView.vue
│ ├── AboutView.vue
│ └── ContactView.vue
├── composables/ # Composition API Funktionen
│ ├── useCounter.js
│ └── useFetch.js
├── stores/ # Pinia Stores
│ ├── counter.js
│ └── user.js
└── utils/ # Hilfsfunktionen
└── helpers.js2. Benennungskonventionen
- Komponenten: PascalCase (
MyComponent.vue) - Composables: camelCase mit
usePräfix (useCounter.js) - Stores: camelCase (
counter.js,user.js)
3. Import/Export Organisation
vue
<script setup>
// Absolute Pfade mit Alias (@/ zeigt auf src/)
import BaseButton from '@/components/BaseButton.vue'
import { useCounter } from '@/composables/useCounter'
import { useUserStore } from '@/stores/user'
// Relativ Pfade nur für eng verwandte Dateien
import HelperComponent from './HelperComponent.vue'
</script>4. Barrierefreieit (Accessibility)
vue
<template>
<button
aria-label="Menü öffnen"
@click="toggleMenu"
>
<span aria-hidden="true">☰</span>
</button>
<nav v-if="isMenuOpen" aria-label="Hauptnavigation">
<a href="#content" tabindex="0">Zum Inhalt</a>
<!-- ... -->
</nav>
</template>39.6 Übung: Modal-Komponente mit Teleport
Aufgabe: Erstelle eine wiederverwendbare Modal-Komponente mit Teleport.
Lösung:
1. BaseModal.vue Komponente erstellen:
vue
<!-- components/BaseModal.vue -->
<script setup>
defineProps({
title: {
type: String,
default: 'Modal'
}
})
const emit = defineEmits(['close'])
</script>
<template>
<teleport to="body">
<div class="modal-overlay" @click.self="emit('close')">
<div class="modal-container">
<div class="modal-header">
<h3>{{ title }}</h3>
<button class="close-btn" @click="emit('close')">×</button>
</div>
<div class="modal-body">
<!-- Slot für Inhalt -->
<slot />
</div>
<div class="modal-footer">
<slot name="footer">
<button @click="emit('close')">Schließen</button>
</slot>
</div>
</div>
</div>
</teleport>
</template>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-container {
background: white;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #eee;
}
.close-btn {
background: none;
border: none;
font-size: 1.5em ;
cursor: pointer;
}
.modal-body {
padding: 20px;
}
.modal-footer {
padding: 15px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>2. Verwendung in App.vue:
vue
<script setup>
import { ref } from 'vue'
import BaseModal from './components/BaseModal.vue'
const isModalOpen = ref(false)
</script>
<template>
<div>
<button @click="isModalOpen = true">Modal öffnen</button>
<BaseModal
v-if="isModalOpen"
title="Mein Modal"
@close="isModalOpen = false"
>
<p>Dies ist der Modal-Inhalt!</p>
<template #footer>
<button @click="isModalOpen = false">Abbrechen</button>
<button @click="isModalOpen = false">OK</button>
</template>
</BaseModal>
</div>
</template>✅ Zusammenfassung
In diesem Kapitel hast du gelernt:
- ✅ Benutzerdefinierte Direktiven erstellen
- ✅ Teleport für Modal-Dialoge verwenden
- ✅ Suspense für asynchrone Komponenten
- ✅ Dynamische Komponenten &
keep-alive - ✅ Modulare Entwicklung & Best Practices
- ✅ Praxis: Modal-Komponente mit Teleport
🎯 Nächster Schritt: In Kapitel 40 lernst du das TodoList-Projekt!
← Zurück zu Kapitel 38: CORSWeiter zu Kapitel 40: TodoList Projekt →
