# Vue 3 avec TypeScript en 2026 : props, emits et composables typés > Les patterns Vue 3 et TypeScript en 2026 : defineProps, defineEmits, composables, defineModel et provide/inject typés, avec exemples concrets et questions d'entretien. - Published: 2026-07-07 - Updated: 2026-07-07 - Author: SharpSkill - Tags: Vue 3, TypeScript, defineProps, Composables, Frontend - Reading time: 8 min --- Vue 3 avec TypeScript en 2026 offre aux composants une vérification statique complète des types, aussi bien sur les props que sur les événements ou la logique réutilisable. La syntaxe ` ``` La prop `role` est désormais contrainte à deux chaînes littérales : passer `role="guest"` fait échouer le build. C'est la raison principale de recourir à `defineProps` avec TypeScript. Le [guide TypeScript officiel de Vue](https://vuejs.org/guide/typescript/composition-api.html) considère d'ailleurs la forme générique comme celle par défaut pour les projets en ` ``` Vue 3.5 a stabilisé la **déstructuration réactive des props**, aujourd'hui le pattern le plus concis. Déstructurer le résultat de `defineProps` en assignant une valeur par défaut dans la même instruction reste pleinement réactif : le compilateur réécrit chaque accès vers `props.x` en coulisses. ```vue ``` > **Le piège de réactivité des props déstructurées** > > Les props déstructurées restent réactives dans le template et dans un `computed`, mais passer une valeur déstructurée directement dans `watch` ou dans un composable la lit une seule fois et rompt le lien réactif. Il faut l'envelopper dans une fonction getter — `watch(() => color, ...)` — ou la convertir avec `toRef(props, 'color')` quand une ref est nécessaire plus loin. C'est un piège classique d'entretien : les candidats supposent que la variable déstructurée est une valeur simple, alors que le compilateur a en réalité redirigé chaque lecture vers `props.color`. ## Des emits typés avec defineEmits Les événements méritent la même rigueur que les props. La forme générique de `defineEmits` décrit chaque nom d'événement et sa charge utile sous forme de tuple, offrant au composant parent l'autocomplétion et au composant enfant la garantie, à la compilation, que les bons arguments sont émis. ```vue ``` Cette forme en tuple a remplacé l'ancienne syntaxe par signature d'appel (`(e: 'search', q: string): void`) parce qu'elle se lit mieux et gère plusieurs événements sans surcharge. Lorsqu'un événement ne transporte aucune donnée, un tuple vide `[]` le documente explicitement. Associer des emits typés à des props typées produit des composants dont toute l'interface publique est vérifiable avant l'exécution — la même discipline que celle abordée dans le [guide de l'API de composition Vue](/blog/vue-nuxt/vue-3-composition-api-complete-guide). ## Typer les composables pour la logique réutilisable Les composables sont de simples fonctions : ils suivent donc les règles ordinaires de TypeScript, mais quelques conventions les rendent plus ergonomiques. Il faut annoter explicitement les types `Ref` quand l'inférence n'est pas évidente, et utiliser des génériques lorsqu'un composable enveloppe des données arbitraires comme une réponse d'API. ```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 } } ``` Du côté de l'appel, le paramètre générique rend `data` entièrement typé sans aucune annotation supplémentaire : ```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') ``` Annoter explicitement l'objet de retour (`UseFetchReturn`) vaut ces quelques lignes supplémentaires : cela documente le contrat, empêche les fuites accidentelles de refs internes et fournit aux consommateurs un type unique à importer. Pour des patterns de composables plus avancés comme les surcharges d'arguments et le nettoyage lié au cycle de vie, voir le [guide avancé des composables Vue](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ## Typer defineModel pour la liaison bidirectionnelle `defineModel`, stable depuis Vue 3.4, condense l'ancien duo prop `modelValue` plus emit `update:modelValue` en une seule ref inscriptible. Son paramètre générique type les deux sens de la liaison en même temps. ```vue ``` Les modèles nommés — `defineModel('title')` — correspondent à `v-model:title` et reçoivent le même typage. Cela élimine toute une catégorie de bugs de charge utile incohérente que le pattern manuel prop-et-emit avait l'habitude de masquer. ## Typer les refs de template et les instances de composant Accéder à un nœud du DOM ou à un composant enfant via une `ref` est l'endroit où le code Vue non typé retombe le plus souvent sur `any`. La solution consiste à paramétrer `useTemplateRef` (Vue 3.5+) ou la `ref` elle-même avec le type de l'élément, afin que l'accès aux propriétés soit vérifié contre la vraie interface du DOM. ```vue ``` Pour une référence vers un composant enfant, `InstanceType` extrait le type public du composant, exposant tout ce que l'enfant a déclaré via `defineExpose`. Les appels de méthode du parent vers l'enfant restent ainsi entièrement vérifiés, plutôt que devinés. ```vue ``` ## provide et inject typés avec InjectionKey L'injection de dépendances à travers l'arbre de composants perd son information de type à moins que la clé ne la transporte. `InjectionKey` est un symbole typé qui lie le type d'une valeur à sa clé, de sorte que `provide` et `inject` restent synchronisés sans cast manuel. ```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() ``` Fournir une valeur dont la forme ne correspond pas à `ThemeContext` provoque une erreur de compilation au point d'injection, capturant tout écart entre des composants éloignés avant sa mise en production. ## Questions d'entretien fréquentes sur Vue et TypeScript Les recruteurs cherchent à savoir si le candidat comprend la frontière entre les types de la compilation et le comportement à l'exécution. Quelques questions récurrentes et leurs réponses tranchantes : - **Pourquoi préférer `defineProps()` à la forme objet d'exécution ?** Les props génériques expriment des types union, des signatures de fonction et des formes imbriquées que les déclarations d'exécution ne peuvent pas capturer, et elles suppriment la duplication entre définition de type et définition d'exécution. - **Les props déstructurées sont-elles réactives ?** Oui depuis Vue 3.5+, car le compilateur réécrit l'accès vers `props.x`. Mais une *valeur* déstructurée passée dans `watch` ou un composable n'est lue qu'une fois — il faut utiliser un getter ou `toRef`. - **Comment typer un événement émis sans charge utile ?** Avec un tuple vide : `defineEmits<{ close: [] }>()`. - **Que remplace `defineModel` ?** Le duo prop `modelValue` et emit `update:modelValue`, unifié en une seule ref inscriptible typée. S'entraîner sur une véritable banque de questions aiguise les réflexes que les entretiens récompensent — le [module d'entretien sur les composables Vue](/technologies/vue-nuxt/interview-questions/vue-composables) travaille précisément ces patterns. L'outillage compte aussi : exécuter `vue-tsc` en CI pour que les erreurs de type bloquent les merges, et s'appuyer sur le [manuel TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html) quand les génériques de composables entrent en jeu. L'expérience dans l'éditeur repose sur [l'outillage officiel Volar de Vue](https://github.com/vuejs/language-tools), qui lit ces macros pour signaler les erreurs en ligne. ## Conclusion - Utiliser la forme générique `defineProps()` pour exprimer des types union, des champs optionnels et des props callback que les déclarations d'exécution ne peuvent pas capturer. - Préférer la déstructuration réactive des props avec valeurs par défaut en ligne dès Vue 3.5+, et ne recourir à `withDefaults` que lorsqu'un objet de valeurs par défaut partagé est plus clair. - Typer les événements avec la forme en tuple de `defineEmits` afin que les charges utiles soient vérifiées à la compilation dans l'enfant comme dans le parent. - Annoter explicitement les types de retour des composables et utiliser des génériques pour transmettre de bout en bout les types de données fournis par l'appelant. - Adopter `defineModel()` pour la liaison bidirectionnelle afin de condenser le boilerplate prop-plus-emit en une seule ref typée. - Exécuter `vue-tsc` en intégration continue pour que les régressions de type fassent échouer le build plutôt que d'atteindre la production. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/vue-nuxt/vue-3-typescript-type-safe-props-emits-composables