Appearance
Kapitel 26: Template-Rendering & Event-Handling
📙 Lernziel: Template-Rendering optimieren und Events effizient handhaben!
26.1 List-Rendering: key Attribut (Wichtig!)
key hilft Vue, Elemente eindeutig zu identifizieren.
Warum key wichtig ist:
- ✅ Bessere Performance
- ✅ Korrekte Zustandserhaltung
- ✅ Vermeidet Rendering-Fehler
Richtige Verwendung:
vue
<script setup>
import { ref } from 'vue'
const users = ref([
{ id: 1, name: 'Max' },
{ id: 2, name: 'Anna' },
{ id: 3, name: 'Tom' }
])
</script>
<template>
<!-- ❌ Falsch (Index als key) -->
<li v-for="(user, index) in users" :key="index">
{{ user.name }}
</li>
<!-- ✅ Richtig (Eindeutige ID) -->
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</template>Wichtig:
- ✅ Nutze eindeutige IDs (aus Datenbank)
- ❌ Nutze keinen Index als
key(außer statische Listen)
26.2 Event-Modifikatoren
Event-Modifikatoren vereinfachen Event-Handling.
Wichtige Modifikatoren:
.prevent - Standardverhalten verhindern
vue
<script setup>
const submitForm = () => {
console.log('Formular abgesendet!')
}
</script>
<template>
<!-- ❌ Falsch (mit JavaScript) -->
<form @submit="event.preventDefault(); submitForm()">
<button type="submit">Absenden</button>
</form>
<!-- ✅ Richtig (mit .prevent) -->
<form @submit.prevent="submitForm()">
<button type="submit">Absenden</button>
</form>
</template>.stop - Event-Bubbling stoppen
vue
<script setup>
const outerClick = () => {
console.log('Außen geklickt')
}
const innerClick = () => {
console.log('Innen geklickt')
}
</script>
<template>
<div @click="outerClick()" style="padding: 20px; background: lightgray;">
Außen
<!-- ❌ Ohne .stop - Beide Events werden ausgelöst -->
<button @click="innerClick()">Klick mich</button>
<!-- ✅ Mit .stop - Nur innerClick wird ausgelöst -->
<button @click.stop="innerClick()">Klick mich (gestoppt)</button>
</div>
</template>.once - Event nur einmal ausführen
vue
<script setup>
const handleClick = () => {
console.log('Nur einmal ausführen!')
}
</script>
<template>
<button @click.once="handleClick()">Einmalig klicken</button>
</template>Weitere Modifikatoren
vue
<template>
<!-- .capture - Event in Capture-Phase abfangen -->
<div @click.capture="handleCapture()">Capture</div>
<!-- .self - Nur wenn Event auf eigenem Element ausgelöst -->
<div @click.self="handleSelf()">Self</div>
<!-- .passive - Bessere Scroll-Performance (mobile) -->
<div @scroll.passive="handleScroll()">Passive</div>
</template>26.3 Tasten-Modifikatoren
Tasten-Modifikatoren reagieren nur auf bestimmte Tasten.
Wichtige Modifikatoren:
.enter - Auf Enter-Taste reagieren
vue
<script setup>
import { ref } from 'vue'
const message = ref('')
const sendMessage = () => {
console.log('Nachricht gesendet:', message.value)
message.value = ''
}
</script>
<template>
<!-- ❌ Falsch (mit JavaScript) -->
<input
v-model="message"
@keydown="if (event.key === 'Enter') sendMessage()"
placeholder="Nachricht eingeben..."
/>
<!-- ✅ Richtig (mit .enter) -->
<input
v-model="message"
@keydown.enter="sendMessage()"
placeholder="Nachricht eingeben..."
/>
</template>Weitere Tasten-Modifikatoren
vue
<script setup>
const handleEscape = () => {
console.log('ESC gedrückt - Modal schließen')
}
const handleSpace = () => {
console.log('Leertaste gedrückt')
}
const handleArrowUp = () => {
console.log('Pfeil hoch gedrückt')
}
</script>
<template>
<!-- .esc - ESC-Taste -->
<input @keydown.esc="handleEscape()" placeholder="ESC zum Abbrechen" />
<!-- .space - Leertaste -->
<input @keydown.space="handleSpace()" placeholder="Leertaste" />
<!-- Pfeiltasten -->
<input @keydown.up="handleArrowUp()" placeholder="Pfeil hoch" />
<input @keydown.down="console.log('Pfeil runter')" />
<input @keydown.left="console.log('Pfeil links')" />
<input @keydown.right="console.log('Pfeil rechts')" />
</template>System-Modifikatoren
vue
<script setup>
const handleCtrlClick = () => {
console.log('Strg + Klick')
}
const handleShiftClick = () => {
console.log('Umschalt + Klick')
}
</script>
<template>
<!-- .ctrl, .alt, .shift, .meta (Command) -->
<button @click.ctrl="handleCtrlClick()">Strg + Klick</button>
<button @click.shift="handleShiftClick()">Umschalt + Klick</button>
</template>26.4 Formular-Input-Bindung
v-model bindet Formular-Inputs an Daten.
Beispiele:
Text-Input
vue
<script setup>
import { ref } from 'vue'
const name = ref('')
const message = ref('')
</script>
<template>
<!-- Text Input -->
<input type="text" v-model="name" placeholder="Name" />
<p>Name: {{ name }}</p>
<!-- Textarea -->
<textarea v-model="message" placeholder="Nachricht"></textarea>
<p>Nachricht: {{ message }}</p>
</template>Checkbox
vue
<script setup>
import { ref } from 'vue'
const isActive = ref(false)
const hobbies = ref([])
</script>
<template>
<!-- Einzelne Checkbox -->
<input type="checkbox" v-model="isActive" />
<label>Aktiv</label>
<p>isActive: {{ isActive }}</p>
<!-- Mehrere Checkboxen -->
<input type="checkbox" v-model="hobbies" value="Sport" />
<label>Sport</label>
<input type="checkbox" v-model="hobbies" value="Lesen" />
<label>Lesen</label>
<input type="checkbox" v-model="hobbies" value="Musik" />
<label>Musik</label>
<p>Hobbys: {{ hobbies }}</p>
</template>Radio-Buttons
vue
<script setup>
import { ref } from 'vue'
const gender = ref('')
</script>
<template>
<input type="radio" v-model="gender" value="male" />
<label>Männlich</label>
<input type="radio" v-model="gender" value="female" />
<label>Weiblich</label>
<input type="radio" v-model="gender" value="other" />
<label>Divers</label>
<p>Geschlecht: {{ gender }}</p>
</template>Select
vue
<script setup>
import { ref } from 'vue'
const city = ref('')
const cities = ref([])
</script>
<template>
<!-- Einfach-Select -->
<select v-model="city">
<option value="" disabled>Wählen...</option>
<option value="berlin">Berlin</option>
<option value="munchen">München</option>
<option value="hamburg">Hamburg</option>
</select>
<p>Stadt: {{ city }}</p>
<!-- Multi-Select -->
<select v-model="cities" multiple>
<option value="berlin">Berlin</option>
<option value="munchen">München</option>
<option value="hamburg">Hamburg</option>
</select>
<p>Städte: {{ cities }}</p>
</template>26.5 v-model Modifikatoren
Modifikatoren für v-model:
vue
<script setup>
import { ref } from 'vue'
const message = ref('')
const age = ref(0)
const content = ref('')
</script>
<template>
<!-- .lazy - Aktualisiert erst bei `change` Event (nicht `input`) -->
<input v-model.lazy="message" />
<p>Nachricht (lazy): {{ message }}</p>
<!-- .number - Wandelt Eingabe automatisch in Zahl um -->
<input v-model.number="age" type="number" />
<p>Alter (number): {{ age }} (Typ: {{ typeof age }})</p>
<!-- .trim - Entfernt Leerzeichen am Anfang und Ende -->
<input v-model.trim="content" />
<p>Inhalt (trimmed): "{{ content }}"</p>
</template>26.6 Übung: Formular mit Validierung
Aufgabe: Erstelle ein Anmeldeformular mit Validierung.
Lösung:
vue
<script setup>
import { ref, computed } from 'vue'
const form = ref({
name: '',
email: '',
password: '',
acceptTerms: false
})
const errors = ref({
name: '',
email: '',
password: ''
})
// Validierung
const validateName = () => {
if (!form.value.name.trim()) {
errors.value.name = 'Name ist erforderlich'
return false
}
if (form.value.name.length < 2) {
errors.value.name = 'Name muss mindestens 2 Zeichen lang sein'
return false
}
errors.value.name = ''
return true
}
const validateEmail = () => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!form.value.email.trim()) {
errors.value.email = 'Email ist erforderlich'
return false
}
if (!emailRegex.test(form.value.email)) {
errors.value.email = 'Ungültige Email-Adresse'
return false
}
errors.value.email = ''
return true
}
const validatePassword = () => {
if (!form.value.password) {
errors.value.password = 'Passwort ist erforderlich'
return false
}
if (form.value.password.length < 6) {
errors.value.password = 'Passwort muss mindestens 6 Zeichen lang sein'
return false
}
errors.value.password = ''
return true
}
const isFormValid = computed(() => {
return validateName() && validateEmail() && validatePassword() && form.value.acceptTerms
})
const handleSubmit = () => {
if (isFormValid.value) {
console.log('Formular abgesendet:', form.value)
alert('Anmeldung erfolgreich!')
}
}
</script>
<template>
<form @submit.prevent="handleSubmit()" class="form">
<div class="form-group">
<label>Name:</label>
<input
v-model="form.name"
@blur="validateName()"
:class="{ 'error': errors.name }"
/>
<span v-if="errors.name" class="error-message">{{ errors.name }}</span>
</div>
<div class="form-group">
<label>Email:</label>
<input
v-model="form.email"
@blur="validateEmail()"
:class="{ 'error': errors.email }"
/>
<span v-if="errors.email" class="error-message">{{ errors.email }}</span>
</div>
<div class="form-group">
<label>Passwort:</label>
<input
v-model="form.password"
type="password"
@blur="validatePassword()"
:class="{ 'error': errors.password }"
/>
<span v-if="errors.password" class="error-message">{{ errors.password }}</span>
</div>
<div class="form-group">
<input type="checkbox" v-model="form.acceptTerms" />
<label>Ich akzeptiere die AGB</label>
</div>
<button type="submit" :disabled="!isFormValid">Absenden</button>
</form>
</template>
<style scoped>
.form {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
input.error {
border-color: red;
}
.error-message {
color: red;
font-size: 0.9em;
margin-top: 5px;
}
button {
width: 100%;
padding: 10px;
background: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>✅ Zusammenfassung
In diesem Kapitel hast du gelernt:
- ✅
keyAttribut für List-Rendering - ✅ Event-Modifikatoren (
.prevent,.stop,.once) - ✅ Tasten-Modifikatoren (
.enter,.esc, etc.) - ✅ Formular-Input-Bindung (
v-model) - ✅
v-modelModifikatoren (.lazy,.number,.trim) - ✅ Praxis: Formular mit Validierung
🎯 Nächster Schritt: In Kapitel 27 lernst du Vue Router (Routing-Grundlagen)!
← Zurück zu Kapitel 25: Benutzerdefinierte ComposablesWeiter zu Kapitel 27: Vue Router Grundlagen →
