Skip to content

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:

  • key Attribut für List-Rendering
  • ✅ Event-Modifikatoren (.prevent, .stop, .once)
  • ✅ Tasten-Modifikatoren (.enter, .esc, etc.)
  • ✅ Formular-Input-Bindung (v-model)
  • v-model Modifikatoren (.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 →

Frei für alle Anfänger