Vue.js – Composition API : pourquoi, quand, comment
Depuis le début de cette série sur Vue.js, nous avons construit nos composants à l’aide de l’API Options — une structure claire et classique, où l’on sépare les données (data), les fonctions (methods), les valeurs calculées (computed) ou les réactions (watch). C’est le format que nous avons utilisé jusqu’ici, car il reste simple à lire et à mettre en œuvre.
Mais Vue 3 introduit aussi une autre manière de faire : la Composition API. Elle ne remplace pas l’ancienne, mais propose une organisation plus souple, qui convient bien aux projets modulaires ou aux composants complexes. Les deux approches peuvent cohabiter dans un même projet : il ne s’agit pas de choisir l’une contre l’autre, mais de comprendre ce que cette nouvelle syntaxe peut apporter.
Pourquoi une autre API ?
L’API Options, que nous utilisons depuis le début, fonctionne très bien pour organiser un composant simple ou une interface claire. On sait où sont les données, où sont les méthodes, comment les propriétés calculées s’insèrent dans l’ensemble. C’est lisible, stable, et largement utilisé.
Mais dès qu’un composant devient plus long, avec plusieurs morceaux de logique mêlés (une partie pour les filtres, une autre pour l’affichage, une autre pour le formulaire…), les blocs de code liés à une même fonctionnalité se retrouvent dispersés : une donnée dans data(), une méthode correspondante dans methods, un watch() quelques lignes plus bas. Cela peut nuire à la cohérence et à la lisibilité globale du composant.
C’est là que la Composition API intervient. Elle propose de regrouper la logique par fonctionnalité, plutôt que par type. On n’écrit plus un objet, mais une fonction setup() dans laquelle on déclare manuellement ce que l’on veut rendre réactif (ref, reactive), calculer (computed), ou surveiller (watch, watchEffect). Cela permet de structurer le code différemment, surtout quand on veut isoler certains comportements ou les réutiliser ailleurs.
La Composition API ne change pas ce que Vue est capable de faire. Elle change seulement la manière d’organiser le code, en offrant plus de liberté là où l’API Options impose une structure. Pour les petits composants, ce n’est pas forcément utile. Mais pour des interfaces plus riches, ou des logiques croisées, elle peut vraiment simplifier la lecture, la maintenance… et la sérénité.
Ce que ça change concrètement
Dans l’API Options, tout est rangé dans des blocs bien définis. Si l’on veut déclarer une donnée, une fonction, une valeur calculée et une surveillance, on écrit :
export default {
data() {
return { count: 0 };
},
computed: {
double() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
watch: {
count(newVal) {
console.log("Nouveau count :", newVal);
}
}
}Avec la Composition API, on écrit tout cela dans un seul bloc setup(), ce qui permet de suivre la logique d’un seul tenant :
import { ref, computed, watch } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
const increment = () => { count.value++ };
watch(count, (newVal) => {
console.log("Nouveau count :", newVal);
});
return { count, double, increment };
}
}La première différence, c’est la clarté fonctionnelle : tout est dans setup(), on voit tout ce qui est lié à count au même endroit. Pas besoin de remonter à data() ou de redescendre à watch.
La seconde, c’est le contrôle plus explicite : on utilise ref() pour chaque valeur réactive, on accède aux valeurs avec .value, on retourne ce qu’on veut exposer. Cela demande un peu d’attention au début, mais on gagne vite en souplesse dès que le composant grossit.
Et createApp dans tout ça ?
Depuis le début de cette série, nous avons utilisé createApp() pour démarrer notre application : c’est ce qui permet à Vue d’attacher un composant racine à un élément HTML (#app) et de le rendre vivant. C’est un point d’entrée global, souvent situé dans un fichier main.js.
import { createApp } from 'vue';
import MonApp from './MonApp.vue';
createApp(MonApp).mount('#app');Dans ce cas, MonApp.vue est un composant, et ce qu’il exporte — avec export default { ... } — est l’objet qui le décrit. Que l’on écrive notre composant avec l’API Options (data(), methods, etc.) ou avec la Composition API (setup()), cela ne change rien ici : c’est toujours un composant exporté.
Autrement dit, createApp() n’est pas en concurrence avec setup() ou avec export default. Il est là pour initialiser l’application. setup(), de son côté, sert à décrire la logique d’un composant. Et export default permet simplement à ce composant d’être utilisé ailleurs.
Ce sont donc trois rôles complémentaires : démarrer, structurer, et partager.
Ce qu’on y gagne (ou pas)
Le principal atout de la Composition API, c’est la lisibilité quand le composant devient complexe. Dans un composant long, l’API Options peut disperser la logique : on déclare une donnée dans data(), une fonction dans methods, une autre réaction dans watch. À force, on saute de bloc en bloc pour suivre un même comportement. Avec setup(), on garde tout au même endroit. Si plusieurs variables sont liées, elles sont visibles et manipulées ensemble.
Autre avantage : la réutilisabilité. On peut extraire une logique dans un composable, c’est-à-dire une fonction externe qui retourne des valeurs réactives. Cela permet de factoriser du code, de mieux structurer l’application, et de tester chaque bloc indépendamment.
Mais il y a aussi des contreparties. Le .value à répétition sur chaque ref() peut ralentir la lecture. Le passage à setup() demande un effort d’adaptation si l’on vient du modèle Options. Et pour un petit composant, tout regrouper dans setup() peut paraître plus verbeux qu’un bon vieux data() et une méthode à la suite.
En résumé : si votre composant est simple, l’API Options suffit largement. Si la logique devient dense, ou si vous voulez préparer une architecture plus modulaire, setup() et la Composition API prennent tout leur sens.
Deux styles, une même intention : comparaison côte à côte
Pour mieux saisir les différences, prenons un exemple concret : un utilisateur clique sur une vignette, et la vidéo sélectionnée change. Dans un cas, on utilise data et watch, dans l’autre, ref et watchEffect. Le comportement reste identique.
Avec l’API Options :
export default {
data() {
return {
selectedVideoId: null,
selectedVideo: null,
};
},
watch: {
selectedVideoId(newId) {
this.selectedVideo = `/videos/${newId}.mp4`;
}
}
}Avec la Composition API :
import { ref, watchEffect } from 'vue';
export default {
setup() {
const selectedVideoId = ref(null);
const selectedVideo = ref(null);
watchEffect(() => {
if (selectedVideoId.value) {
selectedVideo.value = `/videos/${selectedVideoId.value}.mp4`;
}
});
return { selectedVideoId, selectedVideo };
}
}Les deux fonctionnent parfaitement. Le second format paraît plus verbeux pour une simple action, mais gagne en clarté si le composant devient plus riche, ou si on veut réutiliser cette logique ailleurs.
Faut-il changer maintenant ?
Pas forcément. Si votre projet est déjà structuré autour de l’API Options, inutile de tout basculer. Vue vous permet de mixer les deux approches, y compris dans un même projet. Vous pouvez même créer de nouveaux composants avec la Composition API, sans toucher à ceux déjà existants.
En revanche, si vous débutez un projet complexe, avec plusieurs modules ou de nombreuses interactions, partir directement avec setup() peut vous faire gagner en clarté et en évolutivité.
Conclusion
La Composition API n’est pas “meilleure” en soi. Elle propose un modèle plus souple, plus proche de l’approche fonctionnelle. Elle brille quand la logique devient dense ou doit être partagée. Mais l’API Options reste parfaitement valable, surtout dans des projets clairs, bien rangés, et où chaque bloc a sa place.
Dans les prochains articles, nous continuerons à utiliser majoritairement l’API Options — parce qu’elle reste plus lisible pour les premières étapes d’apprentissage — tout en introduisant, progressivement, les parallèles utiles avec la Composition API.
