Skip to content

Kapitel 19: Komponenten-Slots

📙 Lernziel: Slots meistern - Inhalte flexibel in Komponenten einfügen!


19.1 Was sind Slots?

Slots ermöglichen es, Inhalte in eine Kind-Komponente einzufügen.

Vergleich mit Props:

  • Props: Daten übergeben (Strings, Zahlen, Objekte)
  • Slots: HTML/Andere Komponenten einfügen

Beispiel-Szenario:

<BaseLayout>
  <template #header>
    <h1>Überschrift</h1>
  </template>
  
  <template #default>
    <p>Hauptinhalt</p>
  </template>
  
  <template #footer>
    <p>Fußzeile</p>
  </template>
</BaseLayout>

19.2 Standard-Slot (Default Slot)

Einfacher Slot:

vue
<!-- components/BaseButton.vue -->
<script setup>
// Keine Logik nötig
</script>

<template>
  <button class="base-button">
    <!-- Slot (Standard) -->
    <slot></slot>
  </button>
</template>

<style scoped>
.base-button {
  padding: 10px 20px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}
</style>

Verwendung:

vue
<script setup>
import BaseButton from './components/BaseButton.vue'
</script>

<template>
  <div>
    <!-- Einfacher Text -->
    <BaseButton>Klick mich!</BaseButton>
    
    <!-- Mit Icon -->
    <BaseButton>
      <span>🚀</span> Starten
    </BaseButton>
    
    <!-- Mit HTML -->
    <BaseButton>
      <strong>Wichtig!</strong> Klick mich
    </BaseButton>
  </div>
</template>

19.3 Benannte Slots (Named Slots)

Mehrere Slots in einer Komponente:

vue
<!-- components/BaseLayout.vue -->
<script setup>
// Keine Logik nötig
</script>

<template>
  <div class="base-layout">
    <!-- Header Slot -->
    <header>
      <slot name="header"></slot>
    </header>
    
    <!-- Main Content (Default Slot) -->
    <main>
      <slot></slot>
    </main>
    
    <!-- Footer Slot -->
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<style scoped>
.base-layout {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  margin: 20px 0;
}

header {
  border-bottom: 1px solid #ddd;
  padding-bottom: 10px;
  margin-bottom: 10px;
}

footer {
  border-top: 1px solid #ddd;
  padding-top: 10px;
  margin-top: 10px;
}
</style>

Verwendung:

vue
<script setup>
import BaseLayout from './components/BaseLayout.vue'
</script>

<template>
  <BaseLayout>
    <!-- Benannter Slot (v-slot:header oder #header) -->
    <template #header>
      <h1>Meine Webseite</h1>
      <p>Willkommen!</p>
    </template>
    
    <!-- Default Slot -->
    <p>Hier ist der Hauptinhalt der Seite.</p>
    <p>Noch mehr Inhalt...</p>
    
    <!-- Benannter Slot (v-slot:footer oder #footer) -->
    <template #footer>
      <p>© 2026 Meine Webseite</p>
      <a href="/impressum">Impressum</a>
    </template>
  </BaseLayout>
</template>

19.4 Slot-Bestückung (Scoped Slot)

Problem: Slot-Inhalt hat keinen Zugriff auf Daten der Kind-Komponente.

Lösung: Scoped Slots - Daten an Slot weitergeben.

Beispiel:

vue
<!-- components/BaseList.vue -->
<script setup>
import { ref } from 'vue'

const items = ref(['Apfel', 'Banane', 'Orange'])
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <!-- Scoped Slot - Daten weitergeben -->
      <slot :item="item" :index="index" :isEven="index % 2 === 0">
        <!-- Fallback (wenn kein Slot bereitgestellt wird) -->
        {{ item }}
      </slot>
    </li>
  </ul>
</template>

<style scoped>
ul {
  list-style: none;
  padding: 0;
}

li {
  padding: 8px;
  border-bottom: 1px solid #ddd;
}

li:nth-child(even) {
  background: #f5f5f5;
}
</style>

Verwendung:

vue
<script setup>
import BaseList from './components/BaseList.vue'
</script>

<template>
  <div>
    <h2>Liste mit benutzerdefinierter Darstellung</h2>
    
    <BaseList>
      <!-- Scoped Slot (v-slot oder #default) -->
      <template #default="{ item, index, isEven }">
        <strong>{{ index + 1 }}.</strong>
        <span :style="{ color: isEven ? 'green' : 'blue' }">
          {{ item }}
        </span>
      </template>
    </BaseList>
  </div>
</template>

19.5 Fallback-Inhalte (Slot-Defaults)

Slots können Standardinhalte haben:

vue
<!-- components/SubmitButton.vue -->
<script setup>
defineProps({
  submitText: {
    type: String,
    default: 'Absenden'
  }
})
</script>

<template>
  <button type="submit" class="submit-button">
    <!-- Fallback-Inhalt -->
    <slot name="icon">
      📨 <!-- Standard-Icon -->
    </slot>
    
    <span>{{ submitText }}</span>
    
    <slot name="right-icon">
<!-- Standard-Icon (rechts) -->
    </slot>
  </button>
</template>

<style scoped>
.submit-button {
  padding: 10px 20px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
}
</style>

Verwendung:

vue
<script setup>
import SubmitButton from './components/SubmitButton.vue'
</script>

<template>
  <div>
    <!-- Nur mit Fallbacks -->
    <SubmitButton submit-text="Senden" />
    
    <!-- Mit benutzerdefinierten Icons -->
    <SubmitButton submit-text="Speichern">
      <template #icon>💾</template>
      <template #right-icon>✅</template>
    </SubmitButton>
  </div>
</template>

19.6 Übung: Card-Komponente mit Slots

Aufgabe: Erstelle eine Card.vue Komponente mit Slots.

Lösung:

vue
<!-- components/Card.vue -->
<script setup>
defineProps({
  title: {
    type: String,
    default: 'Kartentitel'
  }
})
</script>

<template>
  <div class="card">
    <!-- Header Slot -->
    <div class="card-header">
      <slot name="header">
        <h3>{{ title }}</h3>
      </slot>
    </div>
    
    <!-- Default Slot (Content) -->
    <div class="card-content">
      <slot>
        <p>Kein Inhalt vorhanden.</p>
      </slot>
    </div>
    
    <!-- Footer Slot -->
    <div class="card-footer">
      <slot name="footer">
        <button>OK</button>
      </slot>
    </div>
  </div>
</template>

<style scoped>
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  max-width: 400px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.card-header {
  background: #f5f5f5;
  padding: 15px;
  border-bottom: 1px solid #ddd;
}

.card-header h3 {
  margin: 0;
  color: #42b883;
}

.card-content {
  padding: 15px;
  min-height: 100px;
}

.card-footer {
  background: #f5f5f5;
  padding: 10px 15px;
  border-top: 1px solid #ddd;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
</style>

Verwendung:

vue
<script setup>
import Card from './components/Card.vue'
</script>

<template>
  <div>
    <h2>Karten-Beispiel</h2>
    
    <!-- Standard (mit Fallbacks) -->
    <Card title="Einfache Karte" />
    
    <!-- Mit benutzerdefinierten Slots -->
    <Card>
      <template #header>
        <h3>🎉 Benutzerdefinierte Karte</h3>
      </template>
      
      <template #default>
        <p>Dies ist ein benutzerdefinierter Inhalt für die Karte.</p>
        <ul>
          <li>Punkt 1</li>
          <li>Punkt 2</li>
          <li>Punkt 3</li>
        </ul>
      </template>
      
      <template #footer>
        <button @click="alert('Abbrechen')">Abbrechen</button>
        <button @click="alert('Speichern')">Speichern</button>
      </template>
    </Card>
  </div>
</template>

✅ Zusammenfassung

In diesem Kapitel hast du gelernt:

  • ✅ Was Slots sind (Inhalte in Komponenten einfügen)
  • ✅ Standard-Slot (Default Slot)
  • ✅ Benannte Slots (Named Slots)
  • ✅ Scoped Slots (Daten an Slot weitergeben)
  • ✅ Fallback-Inhalte (Slot-Defaults)
  • ✅ Praxis: Card.vue Komponente

🎯 Nächster Schritt: In Kapitel 20 lernst du Komponenten-Lebenszyklus-Hooks!


← Zurück zu Kapitel 18: Provide & InjectWeiter zu Kapitel 20: Lebenszyklus-Hooks →

Frei für alle Anfänger