# Vue 3 met TypeScript in 2026: type-safe props, emits en composables
> Type-safe Vue 3-componenten beheersen met TypeScript: generieke defineProps, defineEmits als tuple, getypeerde composables, defineModel en InjectionKey, plus sollicitatievragen.
- Published: 2026-07-07
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: Vue 3, TypeScript, defineProps, Composables, Frontend
- Reading time: 8 min
---
Vue 3 met TypeScript levert in 2026 volledig statische typecontrole over props, events en herbruikbare logica. De `
{{ props.name }}
{{ props.role }}
```
De prop `role` is nu beperkt tot twee letterlijke strings, waardoor het doorgeven van `role="guest"` de build laat mislukken. Dat is precies de kernreden om naar `defineProps` met TypeScript te grijpen: de [officiële Vue-TypeScript-gids](https://vuejs.org/guide/typescript/composition-api.html) beschouwt de generieke vorm als standaard voor projecten met `
```
Vue 3.5 heeft **reactieve prop-destructurering** gestabiliseerd, inmiddels het beknoptere patroon. Het resultaat van `defineProps` destructureren en in dezelfde instructie een standaardwaarde toekennen blijft volledig reactief — de compiler herschrijft elke toegang achter de schermen terug naar `props.x`.
```vue
{{ label }}
```
> **Reactiviteitsval bij gedestructureerde props**
>
> Gedestructureerde props blijven reactief in het template en in `computed`, maar een gedestructureerde waarde rechtstreeks doorgeven aan `watch` of een composable leest hem eenmalig en verbreekt de reactieve koppeling. Verpak hem in dat geval in een getter — `watch(() => color, ...)` — of converteer hem met `toRef(props, 'color')` wanneer verderop een ref nodig is.
Dit is een veelvoorkomende valkuil in sollicitatiegesprekken: kandidaten gaan ervan uit dat de gedestructureerde variabele een gewone waarde is, terwijl de compiler in werkelijkheid elke leesactie heeft omgeleid naar `props.color`.
## Type-safe events met defineEmits
Events verdienen dezelfde zorgvuldigheid als props. Het generieke `defineEmits` beschrijft elke eventnaam en de bijbehorende payload als een tuple, wat de oudercomponent autoaanvulling geeft en de kindcomponent controles tijdens het compileren dat de juiste argumenten worden uitgezonden.
```vue
```
Deze tuple-vorm verving de oudere aanroepsignatuur-syntaxis (`(e: 'search', q: string): void`), omdat ze prettiger leest en meerdere events zonder overloads ondersteunt. Wanneer een event geen data draagt, documenteert een lege tuple `[]` dat expliciet. Getypeerde events combineren met getypeerde props levert componenten op waarvan de volledige publieke interface vóór runtime verifieerbaar is — dezelfde discipline die de [gids over de Vue Composition API](/blog/vue-nuxt/vue-3-composition-api-complete-guide) behandelt.
## Composables typeren voor herbruikbare logica
Composables zijn gewone functies en volgen dus de normale TypeScript-regels — maar een paar conventies houden ze ergonomisch. Retourneer `Ref`-typen expliciet wanneer de inferentie niet vanzelfsprekend is, en gebruik generics wanneer een composable willekeurige data zoals een API-respons inpakt.
```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 }
}
```
Op de aanroepplek maakt de generieke parameter `data` volledig getypeerd zonder enige extra annotatie:
```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')
```
Het retourobject expliciet annoteren (`UseFetchReturn`) is de paar extra regels waard: het documenteert het contract, voorkomt dat interne refs per ongeluk uitlekken en geeft consumenten één type om te importeren. Voor diepgaandere composable-patronen zoals argument-overloads en levenscyclusbewuste opruiming, zie de [geavanceerde gids over Vue-composables](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns).
## defineModel typeren voor tweewegbinding
`defineModel`, stabiel sinds Vue 3.4, brengt de oude combinatie van de `modelValue`-prop plus de `update:modelValue`-emit samen in één schrijfbare ref. De generieke parameter typeert beide richtingen van de binding in één keer.
```vue
```
Benoemde modellen — `defineModel('title')` — worden toegewezen aan `v-model:title` en krijgen dezelfde typering. Dit elimineert een hele klasse van bugs door niet-overeenkomende payloads die het handmatige prop-en-emit-patroon vroeger verborg.
## Template-refs en componentinstanties typeren
Toegang tot een DOM-knoop of een kindcomponent via een `ref` is de plek waar niet-getypeerde Vue-code het vaakst terugvalt op `any`. De oplossing is om `useTemplateRef` (Vue 3.5+) of de `ref` zelf te parametriseren met het elementtype, zodat toegang tot eigenschappen wordt gecontroleerd tegen de echte DOM-interface.
```vue
```
Voor een verwijzing naar een kindcomponent extraheert `InstanceType` het publieke type van de component en legt bloot wat het kind via `defineExpose` beschikbaar stelt. Zo blijven methodeaanroepen van ouder naar kind volledig gecontroleerd in plaats van gegokt.
```vue
```
## Type-safe provide en inject met InjectionKey
Dependency injection door de componentenboom verliest type-informatie tenzij de sleutel die meedraagt. `InjectionKey` is een getypeerd symbol dat het type van een waarde aan zijn sleutel bindt, zodat `provide` en `inject` synchroon blijven zonder handmatige casts.
```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()
```
Een waarde aanbieden waarvan de vorm niet overeenkomt met `ThemeContext` is een compileerfout op de injectieplek en vangt zo afwijkingen tussen ver uit elkaar liggende componenten af voordat ze in productie komen.
## Veelgestelde sollicitatievragen over Vue en TypeScript
Interviewers peilen of een kandidaat de grens begrijpt tussen typen tijdens het compileren en gedrag tijdens runtime. Een paar terugkerende vragen en de scherpe antwoorden:
- **Waarom `defineProps()` verkiezen boven de runtime-objectvorm?** Generieke props drukken union-typen, functiesignaturen en geneste vormen uit die runtime-declaraties niet kunnen vatten, en ze verwijderen gedupliceerde type- en runtime-definities.
- **Zijn gedestructureerde props reactief?** Ja, in Vue 3.5+, omdat de compiler toegang herschrijft naar `props.x`. Maar gedestructureerde *waarden* die aan `watch` of een composable worden doorgegeven, worden eenmalig gelezen — gebruik een getter of `toRef`.
- **Hoe typeer je een uitgezonden event zonder payload?** Met een lege tuple: `defineEmits<{ close: [] }>()`.
- **Wat vervangt `defineModel`?** Het paar bestaande uit de `modelValue`-prop en de `update:modelValue`-emit, verenigd in één getypeerde schrijfbare ref.
Deze vragen oefenen tegen een echte vragenbank scherpt de reflexen die sollicitatiegesprekken belonen — de [interviewmodule over Vue-composables](/technologies/vue-nuxt/interview-questions/vue-composables) traint precies deze patronen. Ook tooling telt: draai `vue-tsc` in de CI zodat typefouten merges blokkeren, en leun op het [TypeScript-handboek](https://www.typescriptlang.org/docs/handbook/2/generics.html) wanneer composable-generics in het spel komen. De editorervaring wordt aangedreven door [Vue's officiële Volar-tooling](https://github.com/vuejs/language-tools), die deze macro's leest om fouten inline te tonen.
## Conclusie
- Gebruik het generieke `defineProps()` om union-typen, optionele velden en callback-props uit te drukken die runtime-declaraties niet kunnen vastleggen.
- Verkies in Vue 3.5+ reactieve prop-destructurering met inline standaardwaarden, en grijp alleen naar `withDefaults` wanneer een gedeeld object met standaardwaarden duidelijker is.
- Typeer events met de tuple-vorm van `defineEmits` zodat payloads tijdens het compileren worden gecontroleerd in zowel het kind als de ouder.
- Annoteer retourtypen van composables expliciet en gebruik generics om door de aanroeper aangeleverde datatypen van begin tot eind door te geven.
- Adopteer `defineModel()` voor tweewegbinding om de prop-plus-emit-boilerplate terug te brengen tot één getypeerde ref.
- Draai `vue-tsc` in continuous integration zodat typeregressies de build laten mislukken in plaats van de productie te bereiken.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/nl/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables