# Vue 3 mit TypeScript im Jahr 2026: Typsichere Props, Emits und Composables
> Typsichere Vue-3-Komponenten mit TypeScript meistern: generisches defineProps, defineEmits als Tuple, typisierte Composables, defineModel und InjectionKey samt Interviewfragen.
- Published: 2026-07-07
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: Vue 3, TypeScript, defineProps, Composables, Frontend
- Reading time: 8 min
---
Vue 3 mit TypeScript liefert im Jahr 2026 eine vollständig statische Typprüfung über Props, Events und wiederverwendbare Logik hinweg. Die Syntax `
{{ props.name }}
{{ props.role }}
```
Die Prop `role` ist nun auf zwei literale Zeichenketten beschränkt, sodass das Übergeben von `role="guest"` den Build scheitern lässt. Genau das ist der Kerngrund, zu `defineProps` mit TypeScript zu greifen: Der [offizielle Vue-TypeScript-Leitfaden](https://vuejs.org/guide/typescript/composition-api.html) behandelt die generische Form als Standard für Projekte mit `
```
Vue 3.5 hat das **reaktive Prop-Destructuring** stabilisiert, das inzwischen das prägnantere Muster darstellt. Wird das Ergebnis von `defineProps` destrukturiert und im selben Ausdruck ein Standardwert zugewiesen, bleibt der Zugriff vollständig reaktiv — der Compiler schreibt jeden Zugriff im Hintergrund wieder auf `props.x` um.
```vue
{{ label }}
```
> **Reaktivitätsfalle beim Destructuring von Props**
>
> Destrukturierte Props bleiben im Template und in `computed` reaktiv, aber das direkte Übergeben eines destrukturierten Werts an `watch` oder ein Composable liest ihn nur einmal und trennt die reaktive Verbindung. Für solche Fälle empfiehlt sich ein Getter — `watch(() => color, ...)` — oder die Umwandlung mit `toRef(props, 'color')`, wenn nachgelagert eine Ref benötigt wird.
Dies ist eine häufige Interview-Falle: Kandidatinnen und Kandidaten nehmen an, die destrukturierte Variable sei ein einfacher Wert, während der Compiler in Wahrheit jeden Lesezugriff wieder auf `props.color` umgeleitet hat.
## Typsichere Events mit defineEmits
Events verdienen dieselbe Sorgfalt wie Props. Das generische `defineEmits` beschreibt jeden Event-Namen und dessen Nutzlast als Tuple und liefert der Elternkomponente Autovervollständigung sowie der Kindkomponente Prüfungen zur Kompilierzeit, dass die richtigen Argumente ausgelöst werden.
```vue
```
Diese Tuple-Form ersetzte die ältere Aufrufsignatur-Syntax (`(e: 'search', q: string): void`), weil sie besser lesbar ist und mehrere Events ohne Überladungen unterstützt. Wenn ein Event keine Daten transportiert, dokumentiert ein leeres Tuple `[]` dies ausdrücklich. Die Kombination typisierter Events mit typisierten Props erzeugt Komponenten, deren gesamte öffentliche Schnittstelle vor der Laufzeit überprüfbar ist — dieselbe Disziplin, die auch im [Leitfaden zur Vue Composition API](/blog/vue-nuxt/vue-3-composition-api-complete-guide) behandelt wird.
## Composables für wiederverwendbare Logik typisieren
Composables sind gewöhnliche Funktionen und folgen daher den normalen TypeScript-Regeln — einige Konventionen halten sie jedoch ergonomisch. Es empfiehlt sich, `Ref`-Typen explizit zurückzugeben, wenn die Inferenz nicht offensichtlich ist, und Generics zu verwenden, wenn ein Composable beliebige Daten wie eine API-Antwort kapselt.
```ts
// useFetch.ts
import { ref, type Ref } from 'vue'
interface UseFetchReturn {
data: Ref
error: Ref
loading: Ref
}
// Generic flows through to the caller's typed data
export function useFetch(url: string): UseFetchReturn {
const data = ref(null) as Ref
const error = ref(null)
const loading = ref(true)
fetch(url)
.then((r) => r.json())
.then((json: T) => { data.value = json })
.catch((e: Error) => { error.value = e })
.finally(() => { loading.value = false })
return { data, error, loading }
}
```
An der Aufrufstelle macht der Generics-Parameter `data` vollständig typisiert, ganz ohne zusätzliche Annotation:
```ts
// UserList.vue (script setup)
interface User { id: number; name: string }
// data is Ref — inferred from the generic
const { data: users, loading } = useFetch('/api/users')
```
Die explizite Annotation des Rückgabeobjekts (`UseFetchReturn`) ist die wenigen zusätzlichen Zeilen wert: Sie dokumentiert den Vertrag, verhindert das versehentliche Leaken interner Refs und liefert Konsumenten einen einzigen Typ zum Importieren. Für tiefergehende Composable-Muster wie Argumentüberladungen und lebenszyklusbewusste Bereinigung siehe den [Leitfaden zu fortgeschrittenen Vue-Composables](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns).
## defineModel für bidirektionale Bindung typisieren
`defineModel`, stabil seit Vue 3.4, fasst die alte Kombination aus `modelValue`-Prop und `update:modelValue`-Emit in einer einzigen beschreibbaren Ref zusammen. Der Generics-Parameter typisiert beide Richtungen der Bindung auf einmal.
```vue
```
Benannte Modelle — `defineModel('title')` — werden auf `v-model:title` abgebildet und erhalten dieselbe Typisierung. Damit entfällt eine ganze Klasse von Fehlern durch nicht zueinander passende Nutzlasten, die das manuelle Muster aus Prop und Emit früher verbarg.
## Template-Refs und Komponenteninstanzen typisieren
Der Zugriff auf einen DOM-Knoten oder eine Kindkomponente über eine `ref` ist die Stelle, an der untypisierter Vue-Code am häufigsten auf `any` zurückfällt. Die Lösung besteht darin, `useTemplateRef` (Vue 3.5+) oder die `ref` selbst mit dem Elementtyp zu parametrisieren, sodass der Eigenschaftszugriff gegen die echte DOM-Schnittstelle geprüft wird.
```vue
```
Für eine Referenz auf eine Kindkomponente extrahiert `InstanceType` den öffentlichen Typ der Komponente und legt offen, was das Kind über `defineExpose` bereitstellt. So bleiben Methodenaufrufe von der Eltern- zur Kindkomponente vollständig geprüft statt geraten.
```vue
```
## Typsicheres provide und inject mit InjectionKey
Dependency Injection über den Komponentenbaum hinweg verliert Typinformationen, sofern der Schlüssel sie nicht mitführt. `InjectionKey` ist ein typisiertes Symbol, das den Typ eines Werts an seinen Schlüssel bindet, sodass `provide` und `inject` ohne manuelle Casts synchron bleiben.
```ts
// theme-key.ts
import type { InjectionKey, Ref } from 'vue'
export interface ThemeContext {
mode: Ref<'light' | 'dark'>
toggle: () => void
}
// The key permanently associates the ThemeContext type with this symbol
export const ThemeKey: InjectionKey = Symbol('theme')
```
```ts
// provider (script setup) — value must match ThemeContext or it fails to compile
provide(ThemeKey, { mode, toggle })
// consumer — theme is inferred as ThemeContext | undefined
const theme = inject(ThemeKey)
theme?.toggle()
```
Das Bereitstellen eines Werts, dessen Struktur nicht zu `ThemeContext` passt, ist ein Kompilierfehler an der Injektionsstelle und fängt so Abweichungen zwischen weit entfernten Komponenten ab, bevor sie ausgeliefert werden.
## Häufige Interviewfragen zu Vue und TypeScript
Interviewende prüfen, ob eine Kandidatin oder ein Kandidat die Grenze zwischen Typen zur Kompilierzeit und Verhalten zur Laufzeit versteht. Einige wiederkehrende Fragen und ihre prägnanten Antworten:
- **Warum ist `defineProps()` der Laufzeit-Objektform vorzuziehen?** Generische Props drücken Union-Typen, Funktionssignaturen und verschachtelte Strukturen aus, die Laufzeit-Deklarationen nicht abbilden können, und sie beseitigen doppelte Typ- und Laufzeitdefinitionen.
- **Sind destrukturierte Props reaktiv?** Ja, in Vue 3.5+, weil der Compiler den Zugriff auf `props.x` umschreibt. Doch destrukturierte *Werte*, die an `watch` oder ein Composable übergeben werden, werden nur einmal gelesen — hier hilft ein Getter oder `toRef`.
- **Wie typisiert man ein ausgelöstes Event ohne Nutzlast?** Mit einem leeren Tuple: `defineEmits<{ close: [] }>()`.
- **Was ersetzt `defineModel`?** Das Paar aus `modelValue`-Prop und `update:modelValue`-Emit, vereinigt in einer einzigen typisierten, beschreibbaren Ref.
Diese Fragen an einer echten Fragensammlung zu üben schärft die Reflexe, die Interviews belohnen — das [Interview-Modul zu Vue-Composables](/technologies/vue-nuxt/interview-questions/vue-composables) trainiert genau diese Muster. Auch das Tooling zählt: `vue-tsc` in der CI ausführen, damit Typfehler Merges blockieren, und bei Composable-Generics auf das [TypeScript-Handbuch](https://www.typescriptlang.org/docs/handbook/2/generics.html) zurückgreifen. Das Editor-Erlebnis wird von [Vues offiziellem Volar-Tooling](https://github.com/vuejs/language-tools) angetrieben, das diese Makros liest, um Fehler inline anzuzeigen.
## Fazit
- Generisches `defineProps()` verwenden, um Union-Typen, optionale Felder und Callback-Props auszudrücken, die Laufzeit-Deklarationen nicht erfassen können.
- In Vue 3.5+ reaktives Prop-Destructuring mit Inline-Standardwerten bevorzugen und `withDefaults` nur dann heranziehen, wenn ein gemeinsam genutztes Objekt mit Standardwerten übersichtlicher ist.
- Events mit der Tuple-Form von `defineEmits` typisieren, damit Nutzlasten sowohl im Kind als auch in der Elternkomponente zur Kompilierzeit geprüft werden.
- Rückgabetypen von Composables explizit annotieren und Generics nutzen, um vom Aufrufer gelieferte Datentypen durchgängig weiterzureichen.
- `defineModel()` für bidirektionale Bindung einsetzen, um den Boilerplate aus Prop und Emit auf eine einzige typisierte Ref zu reduzieren.
- `vue-tsc` in der kontinuierlichen Integration ausführen, damit Typregressionen den Build scheitern lassen, statt in die Produktion zu gelangen.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/de/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables