mit TypeScript: defineSlots
Ein Scoped Slot transportiert Daten von der Kindkomponente zurück ins Template des Elternteils, doch ohne explizite Typisierung sieht TypeScript diese Daten lange Zeit nur als any. Ein Tippfehler wie item.näm statt item.name fällt dann erst im Browser auf, nicht schon beim Kompilieren. defineSlots
Inhaltsverzeichnis
- 1. Das Problem mit untypisierten Slots: eine lange Zeit bestehende Typlücke
- 2. defineSlots
(): Grundlagen und Syntax - 3. Scoped-Slot-Props typisieren: ein praktisches Beispiel
- 4. IDE-Autovervollständigung im Template des Elternteils
- 5. Optionale Slots: Fallstricke bei optionalen Einträgen und v-if-Prüfung
- 6. Slots ohne Scoped Props typisieren: reine Inhalts-Slots
- 7. Mehrere benannte Slots gleichzeitig typisieren
- 8. Unterschied zwischen typisierten und untypisierten Slots in der Praxis
- 9. Häufige Fehler und Fazit
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit untypisierten Slots: eine lange Zeit bestehende Typlücke
Während Props über defineProps
Diese Lücke war besonders ärgerlich, weil Scoped Slots häufig genau dort eingesetzt werden, wo individuelles, komponentenspezifisches Rendering gefragt ist, etwa in Listen- oder Tabellen-Komponenten, bei denen der Elternteil pro Zeile eigenes Markup definiert. Gerade dort hätte Typsicherheit besonders viel Wert geliefert, weil ein Tippfehler im Property-Namen sonst erst durch manuelles Testen im Browser auffiel, statt bereits im Editor rot unterstrichen zu werden.
2. defineSlots(): Grundlagen und Syntax
Seit Vue 3.3 steht mit defineSlots
Ähnlich wie bei defineProps und defineEmits handelt es sich um ein reines Compile-Time-Konstrukt, das zur Laufzeit keinerlei zusätzlichen Code erzeugt und keine Laufzeit-Validierung durchführt. Die gesamte Wirkung von defineSlots entfaltet sich ausschließlich innerhalb der TypeScript-Typprüfung, sowohl innerhalb der Komponente selbst als auch, entscheidend, im Template jeder Komponente, die diese Komponente später einbindet.
<script setup lang="ts">
interface Product {
id: string
name: string
price: number
}
defineProps<{ items: Product[] }>()
defineSlots<{
default(props: { item: Product; index: number }): void
}>()
</script>
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
<slot :item="item" :index="index" />
</li>
</ul>
</template>
3. Scoped-Slot-Props typisieren: ein praktisches Beispiel
In der Praxis empfiehlt es sich, den Typ der Scoped-Slot-Props als eigenständiges Interface oder als Typalias auszulagern, sobald mehrere Slots dieselbe Datenstruktur teilen oder der Typ komplex genug wird, um innerhalb der defineSlots-Deklaration unübersichtlich zu werden. Für eine Produktliste ließe sich also zunächst ein Typ ProductSlotProps definieren, der anschließend sowohl in defineSlots als auch, falls nötig, an anderer Stelle in der Komponente wiederverwendet wird.
Wichtig ist, dass die im Slot tatsächlich per :item="item" übergebenen Daten im
<script setup lang="ts">
interface Product {
id: string
name: string
price: number
}
interface ProductSlotProps {
item: Product
index: number
isLast: boolean
}
defineProps<{ items: Product[] }>()
defineSlots<{
default(props: ProductSlotProps): void
}>()
</script>
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
<slot :item="item" :index="index" :is-last="index === items.length - 1" />
</li>
</ul>
</template>
4. IDE-Autovervollständigung im Template des Elternteils
Der eigentliche Mehrwert von defineSlots zeigt sich nicht in der Kindkomponente selbst, sondern im Template jeder Komponente, die den typisierten Slot verwendet. Volar liest die defineSlots-Deklaration aus und leitet daraus den Typ der Destrukturierung innerhalb von ab, sodass die IDE für item.name oder item.price vollständige Autovervollständigung anbietet, inklusive Typinformationen beim Hovern über die Variable.
Ein Tippfehler wie item.näm wird dabei sofort als roter Fehler im Editor markiert, lange bevor die Anwendung überhaupt gestartet wird, was gerade in größeren Projekten mit vielen wiederverwendeten Listen- und Tabellen-Komponenten den Unterschied zwischen einem stillen Bug im Produktivsystem und einem sofort sichtbaren Kompilierfehler ausmacht. Diese Typprüfung funktioniert dabei sowohl für benannte Slots als auch für den Default-Slot gleichermaßen zuverlässig.
5. Optionale Slots: Fallstricke bei optionalen Einträgen und v-if-Prüfung
Nicht jeder Slot muss von jeder einbindenden Komponente tatsächlich befüllt werden, etwa ein optionaler footer-Slot für eine Karten-Komponente, der nur manchmal genutzt wird. In defineSlots wird ein solcher Slot mit einem Fragezeichen nach dem Namen als optional markiert, also footer?(props: FooterSlotProps): void, wodurch TypeScript weiß, dass dieser Slot in der einbindenden Komponente fehlen darf, ohne einen Fehler zu melden.
Ein häufiger Fallstrick dabei ist, im Template der Kindkomponente per v-if="$slots.footer" zu prüfen, ob der Slot überhaupt befüllt wurde, bevor der umschließende Wrapper gerendert wird, etwa um unnötiges leeres Markup zu vermeiden. Diese Prüfung funktioniert unabhängig von der TypeScript-Typisierung rein zur Laufzeit über das $slots-Objekt und muss zusätzlich zur optionalen Typdeklaration in defineSlots gepflegt werden, weil defineSlots ausschließlich die Typebene beschreibt, aber keine automatische Laufzeit-Prüfung erzeugt, ob ein optionaler Slot tatsächlich verwendet wurde.
6. Slots ohne Scoped Props typisieren: reine Inhalts-Slots
Nicht jeder Slot transportiert Daten von der Kindkomponente zurück, viele Slots dienen einfach nur als Platzhalter für beliebiges, statisches Markup, das der Elternteil einfügt, ohne dass die Kindkomponente selbst Daten dafür liefert, etwa ein simpler header-Slot in einer Karten-Komponente. Für solche reinen Inhalts-Slots wird in defineSlots einfach ein leeres Props-Objekt deklariert, etwa header(props: {}): void, oder, falls gar keine Slot-Props existieren sollen, direkt header(): void.
Auch für diese einfachen Fälle lohnt sich die explizite Deklaration in defineSlots, weil sie zusätzlich dokumentiert, welche Slots eine Komponente überhaupt anbietet, unabhängig davon, ob sie Daten transportieren. Für einbindende Komponenten liefert Volar dadurch auch bei reinen Inhalts-Slots eine Autovervollständigung für die verfügbaren Slot-Namen selbst, etwa beim Tippen von #head im Editor, was besonders bei Komponenten mit vielen benannten Slots die Entdeckbarkeit deutlich verbessert.
7. Mehrere benannte Slots gleichzeitig typisieren
Eine typische Karten- oder Layout-Komponente bietet oft mehrere benannte Slots gleichzeitig an, etwa header, default und footer, von denen jeder eine eigene, möglicherweise unterschiedliche Menge an Scoped-Slot-Props transportiert. defineSlots akzeptiert dafür ein Objekt mit mehreren Einträgen gleichzeitig, wobei jeder Key für einen Slot-Namen steht und jeder Wert unabhängig von den anderen typisiert werden kann, ganz ohne dass sich die Slots gegenseitig beeinflussen.
Diese Struktur macht defineSlots besonders wertvoll für Layout-Komponenten mit komplexer Slot-Architektur, weil eine einzige, zentrale Typdeklaration am Anfang der Komponente sofort dokumentiert, welche Slots überhaupt existieren, welche davon Scoped-Slot-Props mitbringen, und welche davon optional sind. Für neue Teammitglieder ersetzt diese Deklaration in der Praxis oft eine separate Dokumentation der Komponenten-API, weil sie direkt im Code steht und automatisch aktuell bleibt.
8. Unterschied zwischen typisierten und untypisierten Slots in der Praxis
Ohne defineSlots funktionieren Scoped Slots weiterhin einwandfrei zur Laufzeit, Vue benötigt keine Typdeklaration, um Daten korrekt an den Slot weiterzureichen. Der gesamte Unterschied spielt sich ausschließlich auf der Ebene der statischen Typprüfung ab: Ohne defineSlots sind Slot-Props implizit any, das Editor-Erlebnis liefert keine Autovervollständigung, und Tippfehler in Property-Namen werden erst zur Laufzeit sichtbar, wenn überhaupt, weil ein fehlendes Property im Template schlicht als undefined gerendert wird, ohne jede Warnung.
Mit defineSlots verschiebt sich ein großer Teil der Fehlererkennung vom Testen im Browser hin zum Kompilierzeitpunkt, was gerade bei häufig wiederverwendeten, generischen Komponenten wie Listen, Tabellen oder Layout-Wrappern spürbar Zeit spart. Der Aufwand für die zusätzliche Typdeklaration ist dabei gering, meist nur wenige Zeilen, während der Nutzen mit jeder weiteren Stelle wächst, an der die Komponente später im Projekt eingebunden wird.
9. Häufige Fehler und Fazit
Der häufigste Fehler ist, defineSlots mit einem Laufzeit-Argument statt einer reinen Typangabe aufzurufen, was schlicht nicht funktioniert, weil das Makro ausschließlich generische Typinformationen akzeptiert und zur Laufzeit keinen eigenen Code generiert. Ein zweiter häufiger Fehler ist, die Slot-Bindung im
Als Fazit gilt: defineSlots
| Slot-Art | defineSlots-Syntax | Optional? | Typischer Einsatz |
|---|---|---|---|
| Default-Slot mit Scoped Props | default(props: T): void | Nein, meist Pflicht | Listen- und Tabellenzeilen individuell rendern |
| Benannter Slot mit Scoped Props | footer(props: T): void | Ja, per footer?(...) | Optionale Fusszeile mit Zusatzdaten |
| Reiner Inhalts-Slot ohne Props | header(): void | Ja, per header?(): void | Statisches Markup ohne Datenübergabe |
| Mehrere Slots gleichzeitig | { default(...); footer?(...) } | Gemischt möglich | Layout-Komponenten mit mehreren Bereichen |
Mironsoft
Vue-Architektur, Composition API und Nuxt-Performance
Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?
Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.
Architektur-Review
Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.
Performance-Audit
Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.
Nuxt-Integration
SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.
10. Zusammenfassung
Slots mit TypeScript typisieren: Das Wichtigste auf einen Blick
Syntax
defineSlots
Wirkung
Reines Compile-Time-Konstrukt, wirkt über Volar sowohl in der Komponente als auch im einbindenden Template.
Optionale Slots
Ein Fragezeichen nach dem Slot-Namen markiert ihn als optional, unabhängig von der v-if="$slots.x"-Laufzeitprüfung.
Nutzen
Autovervollständigung für Slot-Props direkt im Elternteil-Template, Tippfehler werden zu Kompilierfehlern.