- Page d'accueil
- /
- Blog
- /
- TypeScript avec Vue 3 : les 4 endroits où il est rentable tout de suite
TypeScript avec Vue 3 : les 4 endroits où il est rentable tout de suite
Vue.js & Nuxt.jsPar Brahim Anjjar · Publié le , mis à jour le · 7 min de lectureTypeScript dans un projet Vue a la réputation de ralentir le travail : plus de code à écrire, plus d’erreurs à corriger, plus d’outillage à configurer. Quand on découvre le duo, le coût semble dépasser le bénéfice, au moins les premiers jours.

Ce n’est pas le cas. Mais le gain n’est pas réparti uniformément. Une partie du TypeScript ajoute du travail pour peu de retour ; une autre évite des heures de débogage. Ce guide se concentre sur les quatre endroits d’un projet Vue 3 où TypeScript rapporte le plus, et explique pourquoi pour chacun.
Pourquoi TypeScript compte plus dans Vue 3 que dans Vue 2
Vue 3 a été réécrit entièrement en TypeScript. La Composition API, la forme générique de defineProps et Pinia ont été pensés avec TypeScript dès le départ, pas ajoutés après coup. L’inférence de types de Vue 3 est assez poussée pour que beaucoup de types soient déduits sans annotation : vous profitez de TypeScript même en en écrivant peu.
Dans Vue 2, le support de TypeScript reposait sur les décorateurs et les composants en classe, une approche plaquée sur le framework. La Composition API de Vue 3 est fondée sur des fonctions, ce qui correspond naturellement au système de types de TypeScript. Le duo est aujourd’hui vraiment agréable à utiliser.
Endroit 1 : les types des réponses d’API
L’investissement TypeScript le plus rentable dans un projet Vue : définir des interfaces pour les réponses de vos API. C’est là que TypeScript attrape le plus sûrement les bugs avant la production.
Sans types, une réponse d’API est typée any. Quand l’équipe backend renomme un champ (user.name devient user.fullName), le code JavaScript compile sans problème. Le bug apparaît à l’exécution, en production, quand un utilisateur voit un champ vide ou un plantage. Avec TypeScript, le même changement provoque une erreur de compilation à chaque endroit où le champ est utilisé, avant le déploiement.
// Sans TypeScript : le backend renomme un champ, rien ne casse à la compilation
const { data: user } = await useFetch('/api/user')
const name = user.value.name // undefined en production après le renommage
// Avec TypeScript : le renommage est détecté partout où le champ est utilisé
interface User {
id: number
fullName: string // anciennement 'name'
email: string
role: 'admin' | 'user' | 'viewer'
}
const { data: user } = await useFetch<User>('/api/user')
// user.value.name ← erreur TypeScript : Property 'name' does not exist
🔑 À RETENIR : les types des réponses d’API sont rentabilisés la première fois qu’un changement côté backend est détecté avant le déploiement, et non en production. Dans une équipe qui travaille avec un backend, cela arrive souvent.
Endroit 2 : les props des composants avec defineProps<{}>
Avec TypeScript, Vue 3 permet des props entièrement typées grâce à la forme générique defineProps<{}>. C’est l’une des intégrations TypeScript les plus propres, tous frameworks confondus : aucun code superflu, une inférence complète.
Le parent reçoit une erreur de type s’il passe une prop du mauvais type. Le template propose l’autocomplétion sur chaque prop. Les props optionnelles sont marquées avec ?. Les types union limitent les valeurs autorisées. Tout cela à partir d’une seule annotation.
// Vue 2 / premières versions de Vue 3 : PropType (verbeux, typage limité)
props: {
userId: { type: Number as PropType<number>, required: true },
status: { type: String as PropType<'active'|'pending'>, required: true },
}
// Vue 3 + TypeScript : defineProps générique (propre, entièrement vérifié)
const props = defineProps<{
userId: number
label: string
isActive?: boolean // optionnelle
status: 'active' | 'pending' | 'inactive' // union
onSelect?: (id: number) => void // callback typé
}>()
// TypeScript infère correctement dans le template et le script :
// props.userId est un number
// props.status est le type union : pas de chaîne arbitraire
💡 ASTUCE : besoin de valeurs par défaut avec defineProps générique ? Utilisez withDefaults() : const props = withDefaults(defineProps<{count?: number}>(), { count: 0 }). Propre, typé, sans PropType à l’exécution.
Endroit 3 : le type de retour des composables
Chaque composable que vous écrivez devrait avoir un type de retour explicite. Sans lui, TypeScript le déduit de l’implémentation : le type de retour change donc en silence quand vous modifiez la logique interne. Un type de retour explicite rend le contrat public visible, et TypeScript le fait respecter.
L’interface de retour sert aussi de documentation. Un développeur qui lit l’appel du composable sait exactement ce qu’il reçoit, sans lire l’implémentation.
// Le contrat est défini explicitement
interface UseListingsReturn {
listings: Ref<Listing[]>
isLoading: Ref<boolean>
error: Ref<Error | null>
fetchListings: (filters?: ListingFilters) => Promise<void>
clearListings: () => void
}
export function useListings(): UseListingsReturn {
const listings = ref<Listing[]>([])
const isLoading = ref(false)
const error = ref<Error | null>(null)
async function fetchListings(filters?: ListingFilters) {
isLoading.value = true
try {
listings.value = await $fetch('/api/listings', { query: filters })
} catch(e) { error.value = e as Error }
finally { isLoading.value = false }
}
function clearListings() { listings.value = [] }
return { listings, isLoading, error, fetchListings, clearListings }
// TypeScript vérifie que le retour correspond à UseListingsReturn
}
Endroit 4 : l’état et les actions des stores Pinia
Pinia avec TypeScript offre l’une des gestions d’état les plus propres, tous frameworks confondus. Pour les stores simples, TypeScript infère tout à partir de la définition du store : aucune déclaration de type manuelle.
Pour les stores à l’état complexe, une interface d’état explicite garantit que toutes les actions respectent le contrat, et donne à chaque composant qui utilise le store une autocomplétion et une vérification de types correctes.
interface ListingFilters {
priceMax: number
bedrooms: number
location: string | null
}
interface ListingsState {
items: Listing[]
selectedId: number | null
filters: ListingFilters
isLoading: boolean
}
export const useListingsStore = defineStore('listings', () => {
const state = reactive<ListingsState>({
items: [],
selectedId: null,
filters: { priceMax: 5000, bedrooms: 1, location: null },
isLoading: false,
})
function selectListing(id: number) {
state.selectedId = id // ✅ TypeScript : id est un number
// state.selectedId = 'abc' // ← erreur de compilation
}
return { ...toRefs(state), selectListing }
})
À éviter : du TypeScript qui coûte sans rapporter
Tout le TypeScript n’apporte pas de valeur. Ces pratiques ajoutent de la complexité sans protection réelle :
- Annoter chaque variable locale quand TypeScript l’infère correctement : const count = ref(0) est déjà un Ref<number>
- Écrire des types génériques complexes pour des cas simples et évidents
- Utiliser 'as any' ou '@ts-ignore' pour faire taire les erreurs : corrigez le type, ne masquez pas l’avertissement
- Créer des alias pour des types primitifs : type UserId = number n’apporte rien
- Préciser le type de retour de fonctions très courtes dont l’inférence est évidente
💡 ASTUCE : TypeScript doit rendre le code plus clair, pas plus difficile à lire. Quand une annotation de type demande plus d’explications que la fonction elle-même, c’est le signe qu’il faut simplifier la conception.
Configurer TypeScript dans un projet Nuxt 3
Nuxt 3 gère TypeScript nativement. Le tsconfig.json est généré automatiquement. Pour le mode strict, celui qui attrape le plus d’erreurs, ajoutez ceci à votre nuxt.config.ts :
// nuxt.config.ts
export default defineNuxtConfig({
typescript: {
strict: true, // active toutes les vérifications strictes
typeCheck: true, // lance tsc en parallèle du serveur de dev
shim: false, // inutile avec Vite
}
})
// Vérification des types seule :
// npx nuxi typecheck
⚠️ ATTENTION : typeCheck: true lance TypeScript à chaque enregistrement de fichier en développement. Sur un gros projet, cela peut ralentir le serveur de dev. Lancez plutôt npx nuxi typecheck en pre-commit ou en CI.
Conclusion
Les quatre endroits où TypeScript rapporte tout de suite dans Vue 3 (types des réponses d’API, defineProps générique, retours des composables et stores Pinia) sont les interfaces de votre application où une incohérence coûte le plus cher. Attraper une erreur de type à la compilation plutôt qu’en production, c’est la différence entre une correction de cinq minutes et un incident en production.
Commencez par les types des réponses d’API. Définissez des interfaces pour vos trois endpoints les plus utilisés et lancez la vérification des types. La première fois qu’elle détecte un changement backend avant le déploiement, TypeScript sera rentabilisé sur ce projet.
Vous voulez TypeScript configuré ainsi dans votre projet Vue 3 ? Je travaille sur des applications Vue.js en tant que développeur Vue.js freelance.
Besoin d'aide sur ce sujet ?
Développeur Nuxt.js freelance

