# Composables Vue 3 Avances : Patterns Reutilisables et Questions d'Entretien 2026 > Guide complet des composables Vue 3 avances : patterns reutilisables, gestion asynchrone, injection de dependances, validation de formulaires et questions d'entretien technique 2026. - Published: 2026-05-12 - Updated: 2026-05-12 - Author: SharpSkill - Tags: vue, composables, composition-api, interview, deep-dive - Reading time: 11 min --- Les composables constituent le mecanisme fondamental de reutilisation de logique dans Vue 3. Depuis l'adoption massive de la Composition API, la capacite a concevoir des composables robustes, typesafe et testables distingue les developpeurs seniors des profils intermediaires lors des entretiens techniques. En 2026, la maitrise de ces patterns depasse le simple usage de `ref` et `computed` : elle implique la gestion fine du cycle de vie, la composition de fonctions reactives et l'architecture modulaire d'applications complexes. Cet article analyse en profondeur les patterns avances de composables Vue 3, depuis l'anatomie d'un composable bien structure jusqu'aux strategies de test, en passant par la gestion asynchrone et l'injection de dependances. Chaque pattern est accompagne d'exemples concrets directement applicables en production et en entretien technique. > **Qu'est-ce qu'un composable ?** > > Un composable est une fonction qui exploite la Composition API de Vue pour encapsuler et reutiliser de la logique stateful. Par convention, les composables commencent par le prefixe `use` (ex. `useCounter`, `useFetchData`). Contrairement aux mixins de Vue 2, les composables offrent un typage explicite, evitent les collisions de noms et rendent les dependances transparentes. ## Anatomie d'un Composable Bien Structure Un composable de qualite production respecte plusieurs principes architecturaux : typage strict des entrees et sorties, separation des responsabilites, et interface predictible. Le composable `useCounter` suivant illustre ces conventions fondamentales. ```typescript // useCounter.ts import { ref, computed, type Ref } from 'vue' interface UseCounterOptions { min?: number max?: number initialValue?: number } interface UseCounterReturn { count: Ref doubled: Ref increment: () => void decrement: () => void reset: () => void } export function useCounter(options: UseCounterOptions = {}): UseCounterReturn { const { min = 0, max = Infinity, initialValue = 0 } = options const count = ref(initialValue) const doubled = computed(() => count.value * 2) function increment() { if (count.value < max) count.value++ } function decrement() { if (count.value > min) count.value-- } function reset() { count.value = initialValue } return { count, doubled, increment, decrement, reset } } ``` Plusieurs elements meritent attention dans cette implementation. L'interface `UseCounterOptions` definit un contrat clair pour la configuration, tandis que `UseCounterReturn` documente precisement ce que le composable expose. Le destructuring avec valeurs par defaut dans le corps de la fonction garantit un comportement previsible meme sans configuration explicite. Le retour d'un objet nomme (plutot qu'un tableau) permet au consommateur de selectionner uniquement les proprietes necessaires via le destructuring. Cette convention, adoptee par l'ensemble de l'ecosysteme Vue, facilite la lisibilite et la maintenance du code appelant. Les bornes `min` et `max` illustrent un pattern recurrent dans les composables de production : la validation des contraintes metier directement dans la logique reactive, evitant ainsi de disperser ces regles dans les composants consommateurs. ## Composables Asynchrones avec Gestion d'Erreurs La gestion de requetes HTTP constitue l'un des cas d'usage les plus frequents des composables. Un composable asynchrone bien concu doit gerer le cycle de vie complet d'une requete : chargement, succes, erreur et annulation. Le pattern suivant integre `watchEffect` pour la reactivite automatique et `AbortController` pour l'annulation propre des requetes. ```typescript // useFetchData.ts import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue' interface UseFetchReturn { data: Ref error: Ref isLoading: Ref refresh: () => Promise } export function useFetchData( url: MaybeRefOrGetter ): UseFetchReturn { const data = ref(null) as Ref const error = ref(null) const isLoading = ref(false) let abortController: AbortController | null = null async function fetchData() { // Cancel any in-flight request abortController?.abort() abortController = new AbortController() isLoading.value = true error.value = null try { const response = await fetch(toValue(url), { signal: abortController.signal }) if (!response.ok) throw new Error(`HTTP ${response.status}`) data.value = await response.json() } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return error.value = err instanceof Error ? err.message : 'Unknown error' } finally { isLoading.value = false } } // Re-fetch when URL changes reactively watchEffect(() => { fetchData() }) onUnmounted(() => abortController?.abort()) return { data, error, isLoading, refresh: fetchData } } ``` L'utilisation de `MaybeRefOrGetter` pour le parametre `url` offre une flexibilite maximale : le consommateur peut passer une chaine statique, une `ref`, ou un getter. La fonction `toValue()` resout automatiquement le type sous-jacent, qu'il s'agisse d'une valeur brute ou reactive. Le mecanisme d'annulation via `AbortController` previent les conditions de course (race conditions), probleme classique lorsqu'une nouvelle requete est declenchee avant la fin de la precedente. Le hook `onUnmounted` garantit le nettoyage des requetes en cours lors de la destruction du composant, evitant les fuites memoire et les mises a jour d'etat sur des composants demontes. La methode `refresh` exposee dans le retour permet au composant consommateur de declencher manuellement un rechargement des donnees, pattern indispensable pour les actions utilisateur comme un bouton "Actualiser". > **Hooks de cycle de vie dans les composables** > > Les hooks de cycle de vie (`onMounted`, `onUnmounted`, etc.) doivent etre appeles de maniere synchrone dans le corps du composable, jamais dans un callback asynchrone ou un `setTimeout`. Vue associe ces hooks a l'instance du composant active au moment de l'appel. Un appel differe peut provoquer des erreurs silencieuses ou rattacher le hook au mauvais composant. ## Composition de Composables La veritable puissance des composables reside dans leur capacite a se composer entre eux. Un composable de haut niveau peut orchestrer plusieurs composables specialises pour creer des fonctionnalites complexes tout en conservant une separation claire des responsabilites. Le pattern suivant combine recherche debouncee, pagination et chargement de donnees. ```typescript // usePaginatedSearch.ts import { ref, computed, watch, type Ref } from 'vue' import { useFetchData } from './useFetchData' import { useDebouncedRef } from './useDebouncedRef' interface UsePaginatedSearchReturn { query: Ref page: Ref results: Ref totalPages: Ref isLoading: Ref error: Ref nextPage: () => void prevPage: () => void } export function usePaginatedSearch( baseUrl: string, perPage = 20 ): UsePaginatedSearchReturn { const query = useDebouncedRef('', 300) const page = ref(1) const totalPages = ref(1) const apiUrl = computed( () => `${baseUrl}?q=${encodeURIComponent(query.value)}&page=${page.value}&limit=${perPage}` ) const { data, error, isLoading } = useFetchData<{ items: T[]; total: number }>(apiUrl) const results = computed(() => data.value?.items ?? null) watch(data, (response) => { if (response) { totalPages.value = Math.ceil(response.total / perPage) } }) // Reset to page 1 when query changes watch(query, () => { page.value = 1 }) function nextPage() { if (page.value < totalPages.value) page.value++ } function prevPage() { if (page.value > 1) page.value-- } return { query, page, results, totalPages, isLoading, error, nextPage, prevPage } } ``` Ce composable demontre plusieurs techniques avancees de composition. Le `useDebouncedRef` encapsule la logique de temporisation, evitant de surcharger l'API avec des requetes a chaque frappe. Le `computed` `apiUrl` reconstruit automatiquement l'URL lorsque la requete ou la page change, declenchant une nouvelle recuperation via `useFetchData`. Le `watch` sur `query` reinitialise la page a 1 lors de chaque nouvelle recherche, comportement attendu par les utilisateurs mais souvent oublie dans les implementations naives. Ce detail illustre l'interet d'encapsuler la logique metier dans des composables plutot que de la disperser dans les composants. La composition de composables suit le principe de responsabilite unique : `useDebouncedRef` gere le debouncing, `useFetchData` gere le cycle requete/reponse, et `usePaginatedSearch` orchestre le tout. Cette architecture facilite le test unitaire de chaque couche independamment. ## Injection de Dependances avec provide/inject Pour les etats partages entre composants eloignes dans l'arbre, le systeme `provide/inject` de Vue offre une alternative elegante au prop drilling. Les composables peuvent encapsuler cette mecanique pour fournir une API propre et typesafe. ```typescript // useTheme.ts import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue' type Theme = 'light' | 'dark' | 'system' interface ThemeContext { theme: Readonly> setTheme: (t: Theme) => void resolvedTheme: Readonly> } const ThemeKey: InjectionKey = Symbol('theme') export function provideTheme(initial: Theme = 'system') { const theme = ref(initial) const resolvedTheme = computed<'light' | 'dark'>(() => { if (theme.value !== 'system') return theme.value return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' }) function setTheme(t: Theme) { theme.value = t } const context: ThemeContext = { theme: readonly(theme), setTheme, resolvedTheme: readonly(resolvedTheme) } provide(ThemeKey, context) return context } export function useTheme(): ThemeContext { const context = inject(ThemeKey) if (!context) { throw new Error('useTheme() requires a parent component to call provideTheme()') } return context } ``` L'utilisation d'un `InjectionKey` type avec `Symbol` garantit l'unicite de la cle d'injection et fournit l'inference de type automatique lors de l'appel a `inject`. Ce pattern elimine les risques de collision de cles qui existaient avec les cles basees sur des chaines de caracteres. La separation entre `provideTheme` et `useTheme` etablit une frontiere architecturale claire : le composant racine (ou layout) appelle `provideTheme` pour initialiser le contexte, tandis que les composants descendants appellent `useTheme` pour le consommer. L'utilisation de `readonly` sur les refs exposees protege contre les mutations accidentelles depuis les consommateurs. Le `resolvedTheme` computed illustre un pattern frequent : transformer une valeur de configuration abstraite (`'system'`) en valeur concrete (`'light'` ou `'dark'`) basee sur les preferences systeme. Ce niveau d'abstraction simplifie considerablement la logique des composants consommateurs. ## Composable de Validation de Formulaires La validation de formulaires represente un cas d'usage complexe qui beneficie enormement de l'encapsulation dans un composable. Le pattern suivant fournit une validation declarative basee sur des regles, avec gestion reactive des erreurs et etat de validite global. ```typescript // useFormValidation.ts import { reactive, computed, type UnwrapNestedRefs } from 'vue' type ValidationRule = (value: T) => string | true type FieldRules = { [K in keyof T]?: ValidationRule[] } interface UseFormReturn> { fields: UnwrapNestedRefs errors: Record isValid: Ref validate: () => boolean resetErrors: () => void } export function useFormValidation>( initialValues: T, rules: FieldRules ): UseFormReturn { const fields = reactive({ ...initialValues }) as UnwrapNestedRefs const errors = reactive( Object.keys(initialValues).reduce( (acc, key) => ({ ...acc, [key]: '' }), {} as Record ) ) function validate(): boolean { let valid = true for (const key of Object.keys(rules) as (keyof T)[]) { const fieldRules = rules[key] || [] errors[key] = '' as any for (const rule of fieldRules) { const result = rule(fields[key]) if (result !== true) { errors[key] = result as any valid = false break // Stop at first error per field } } } return valid } function resetErrors() { for (const key of Object.keys(errors)) { (errors as any)[key] = '' } } const isValid = computed(() => Object.values(errors).every((e) => e === '') ) return { fields, errors, isValid, validate, resetErrors } } ``` Ce composable exploite `reactive` plutot que `ref` pour l'objet `fields`, ce qui permet un acces direct aux proprietes sans `.value`. Le systeme de regles adopte une convention simple : chaque regle retourne `true` en cas de succes ou un message d'erreur sous forme de chaine. L'instruction `break` arrete la validation au premier echec par champ, evitant l'accumulation de messages d'erreur. Le type `FieldRules` exploite les mapped types de TypeScript pour garantir que les regles correspondent aux champs du formulaire. Cette approche detecte les erreurs de typage a la compilation, reduisant les bugs en production. Le `computed` `isValid` offre un indicateur reactif de l'etat global du formulaire, directement utilisable pour activer ou desactiver un bouton de soumission sans logique supplementaire dans le composant. ## Tester les Composables Les composables necessitent un contexte Vue actif pour fonctionner correctement, car ils dependent du systeme de reactivite et des hooks de cycle de vie. La technique standard consiste a creer un composant wrapper minimal qui instancie le composable dans son `setup`. Vitest et Vue Test Utils fournissent les outils necessaires. ```typescript // useCounter.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import { defineComponent, h } from 'vue' import { useCounter } from './useCounter' function withSetup(composable: () => T): { result: T; unmount: () => void } { let result!: T const wrapper = mount( defineComponent({ setup() { result = composable() return () => h('div') } }) ) return { result, unmount: () => wrapper.unmount() } } describe('useCounter', () => { it('initializes with default value', () => { const { result } = withSetup(() => useCounter()) expect(result.count.value).toBe(0) }) it('respects min and max boundaries', () => { const { result } = withSetup(() => useCounter({ min: 0, max: 3, initialValue: 3 }) ) result.increment() expect(result.count.value).toBe(3) // Capped at max result.count.value = 0 result.decrement() expect(result.count.value).toBe(0) // Capped at min }) it('computes doubled value reactively', () => { const { result } = withSetup(() => useCounter({ initialValue: 5 })) expect(result.doubled.value).toBe(10) result.increment() expect(result.doubled.value).toBe(12) }) }) ``` La fonction utilitaire `withSetup` constitue un pattern reutilisable pour tester n'importe quel composable. Elle monte un composant minimal dont le seul role est d'executer le composable dans un contexte reactif valide. Le retour de la methode `unmount` permet de tester le comportement de nettoyage des hooks `onUnmounted`. Les tests couvrent trois aspects essentiels : l'initialisation par defaut, le respect des contraintes metier (bornes min/max), et la reactivite des valeurs calculees. Cette couverture represente le minimum attendu pour un composable de production. Pour les composables asynchrones comme `useFetchData`, les tests necessitent des mocks de `fetch` et l'utilisation de `flushPromises()` pour attendre la resolution des promesses avant les assertions. Ce sujet constitue une question d'entretien classique sur la maitrise des tests asynchrones avec Vue. > **VueUse : la bibliotheque de reference** > > VueUse propose plus de 200 composables couvrant les interactions navigateur, les capteurs, les animations et les utilitaires reactifs. Avant de developper un composable personnalise, il est recommande de verifier si VueUse propose deja une solution. Cependant, comprendre les patterns sous-jacents reste indispensable pour adapter, etendre ou debugger ces composables en production. ## Questions d'Entretien Frequentes Les composables Vue 3 constituent un sujet incontournable des entretiens techniques en 2026. Les questions suivantes couvrent les points evalues par les recruteurs, du niveau intermediaire au niveau senior. **Quelle est la difference entre un composable et un mixin ?** Les mixins Vue 2 fusionnent les options dans le composant, provoquant des collisions de noms, des sources de donnees implicites et une absence de typage. Les composables exposent des interfaces explicites via des valeurs de retour typees, permettent le renommage par destructuring et rendent les dependances transparentes. Les composables sont superieurs en termes de maintenabilite, testabilite et compatibilite TypeScript. **Comment gerer les effets de bord dans un composable ?** Tout effet de bord (ecouteur d'evenement, timer, abonnement WebSocket) doit etre nettoye dans un hook `onUnmounted` ou via le mecanisme de cleanup de `watchEffect`. L'omission de ce nettoyage provoque des fuites memoire et des comportements imprevisibles lors du remontage de composants. **Peut-on appeler un composable en dehors de `setup()` ?** Non. Les composables doivent etre appeles de maniere synchrone dans la fonction `setup()` d'un composant ou dans un autre composable. Un appel en dehors de ce contexte empeche Vue d'associer les hooks de cycle de vie et la reactivite a l'instance du composant. **Comment partager un etat global avec les composables ?** Deux approches principales existent. Le pattern singleton cree une instance reactive au niveau du module (en dehors de la fonction composable) partagee entre tous les consommateurs. Le pattern `provide/inject` utilise l'arbre de composants pour propager un etat contextuel. Le choix depend du cas d'usage : singleton pour un etat veritablement global, provide/inject pour un etat scope a un sous-arbre. **Comment tester un composable qui utilise des hooks de cycle de vie ?** La technique `withSetup` (presentee ci-dessus) cree un composant wrapper minimal qui fournit le contexte Vue necessaire. Pour les hooks comme `onMounted`, le composant doit etre monte dans un DOM reel ou virtuel via Vue Test Utils. Les composables purement reactifs (sans hooks de cycle de vie) peuvent etre testes plus simplement avec `effectScope` de Vue. **Quelle strategie adopter pour les composables asynchrones en SSR ?** En contexte SSR (Nuxt ou rendu serveur Vue), les composables asynchrones necessitent une attention particuliere. `onMounted` ne s'execute pas cote serveur, et les appels `fetch` doivent etre geres via les mecanismes SSR du framework (comme `useAsyncData` dans Nuxt). Le composable doit distinguer l'environnement d'execution et adapter son comportement en consequence. ## Conclusion Les composables Vue 3 representent bien plus qu'une evolution syntaxique par rapport aux mixins : ils incarnent un changement de paradigme dans la facon de structurer la logique reutilisable dans les applications frontend. Leur maitrise constitue un avantage competitif significatif, tant en production qu'en entretien technique. Les points essentiels a retenir : - **Typage strict** : definir des interfaces explicites pour les options et les valeurs de retour renforce la fiabilite et l'experience developpeur - **Gestion du cycle de vie** : toujours nettoyer les effets de bord dans `onUnmounted` pour eviter les fuites memoire - **Composition** : concevoir des composables specialises qui se combinent plutot que des composables monolithiques - **Reactivite fine** : exploiter `MaybeRefOrGetter`, `toValue` et `watchEffect` pour maximiser la flexibilite - **Injection de dependances** : utiliser `provide/inject` avec des `InjectionKey` types pour les etats partages dans l'arbre de composants - **Testabilite** : la technique `withSetup` fournit un contexte reactif minimal pour valider chaque composable independamment - **Validation** : encapsuler les regles metier dans des composables de validation garantit leur coherence a travers l'application La capacite a concevoir, composer et tester des composables robustes distingue les profils seniors sur le marche Vue en 2026. Ces patterns constituent le socle technique sur lequel reposent les applications Vue 3 et Nuxt 3 de 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/advanced-vue-3-composables-reusable-patterns