Formation Vue 3 | Structurer son Code avec les Composants
Une application Vue.js est découpée en une multitude de blocs réutilisables et autonomes nommés composants.
Qu’est-ce qu’un composant ?
Diviser son UI
Un composant représente l’unité fondamentale de construction d’interface utilisateur (UI) du framework réactif Vue.js.
Les composants Vue.js sont des blocs de code réutilisables qui encapsulent la structure, la logique et l’apparence d’une partie de l’interface utilisateur d’une application Vue.js.
Les composants Vue.js sont ainsi généralement composés :
- D’un template HTML pour la structure
- D’un script JavaScript pour la logique
- D’un style CSS pour l’apparence
Un composant peut être considéré comme une « boîte noire » qui :
- Prend des données en entrée (appelées « props » ou propriétés)
- Produit un template réactif en sortie
Ils peuvent être imbriqués et réutilisés dans d’autres composants, ce qui permet de créer des interfaces utilisateur complexes et modulaires. Une application réactive de grande ampleur est généralement constituée de multiples composants imbriqués les uns dans les autres, formant un arbre, à la manière du DOM.
Leur comportement est autonome, ce qui signifie qu’ils ne dépendent pas de l’état global de l’application.
Phase de build
Jusqu’à présent, nous avons pris en main Vue.js en chargeant le framework via un lien CDN. Si cela s’est avéré très pratique car aucune configuration n’a été requise, cela nous limite grandement en termes de fonctionnalités et de capacités.
Il conviendra alors de développer notre application de manière plus professionnelle via l’exploitation d’un serveur Node.js et de l’outil Vite.
Cet environnement nous permettra de construire notre application de manière plus efficace et de l’optimiser pour la production en bénéficiant d’une phase de build côté serveur.
Single File Components (SFC)
Lorsque nous utilisons Vue.js avec une phase de build (avec Vite et non via un lien CDN), on déclare nos composants dans des templates portant l’extension .vue
, on parle de composants monofichiers ou Single File Components (SFC).
Les SFC contiennent à la fois :
- Du HTML au sein de balises
<template>
- Du JavaScript au sein de balises
<script>
- Du CSS au sein de balises
<style>
<template></template>
<script></script>
<style></style>
Afin de les différencier de templates et composants HTML classiques, on déclare systématiquement nos fichiers de composants en PascalCase
.
Par exemple : MainNav.vue
, DropZone.vue
…
Ces fichiers ne peuvent pas être utilisés directement dans une application web, car les navigateurs ne comprennent pas leur syntaxe. Ils sont compilés sur le serveur par un outil tel que Vite qui les transformera en fichiers JavaScript simples afin d’être exécutés dans le navigateur.
Options API VS Composition API
Depuis Vue 3, deux approches/syntaxes s’offrent à nous afin d’écrire nos SFC :
- Options API : consiste à définir les options des composants dans un objet.
- Composition API : consiste à organiser la logique des composants en utilisant des fonctions plutôt qu’un objet.
Vous retrouverez plus d’informations sur la documentation officielle de Vue.js ainsi que sur une vidéo de VueSchool.
Le choix entre ces deux approches dépend bien entendu de vos préférences personnelles et de l’architecture de votre application.
Voici un tableau récapitulatif de ce qu’il faut retenir entre Options API VS Composition API.
API | Caractéristiques |
---|---|
Options API | - Historique - Pédagogique - Convient aux petits / moyens projets |
Composition API | - Moderne - Plus structuré - Convient aux gros projets |
Créer un composant
Créons un composant nommé MyCounter.vue
dont le rôle est d’incrémenter un compteur lorsqu’on clique sur un bouton.
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
<style scoped>
button {
display: inline-block;
background-color: #FF0000;
color: #fff;
padding: .75rem 1rem;
border-radius: 6px;
}
</style>
Template
La structure d’un composant se déclare avec la balise <template>
.
Le template regroupe les balises HTML du composant, couplées à d’éventuelles directives Vue.js.
Notez que le balise <template>
ne doit avoir qu’un seul enfant.
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
Script
La logique JS d’un composant se déclare avec la balise <script>
.
Les scripts regroupent l’état et la logique applicative du composant.
export default
permet d’exporter l’objet contenant les options de notre composant.
<script>
export default {
data() {
return {
count: 0
}
}
}
</script>
Style
Le style CSS d’un composant se déclare avec la balise <style>.
Le style regroupe la mise en forme du composant.
L’attribut scoped
permet de limiter la portée du style aux balises HTML internes à ce composant.
<style scoped>
button {
display: inline-block;
background-color: #FF0000;
color: #fff;
padding: .75rem 1rem;
border-radius: 6px;
}
</style>
Composant racine App.vue
📄 App.vue
, situé à la racine du dossier 📂 src
est le composant racine de notre application.
Il s’agit du composant de premier niveau, qui va agir comme conteneur pour tous les autres composants de l’application. Il est ensuite monté sur la div #app
de l’application par l’intermédiaire du fichier 📄 main.js
.
import { createApp } from 'vue'
import App from './App.vue'
// ...
createApp(App).mount('#app')
Enregistrer un composant
Pour utiliser un composant, il faut d’abord l’enregistrer / l’importer. Cet import peut être effectué de manière globale ou locale.
Enregistrement global
Enregistrer un module globalement permet d’en bénéficier depuis n’importe quel composant de notre application.
Cela s’avère très utile pour charger des composants réutilisables dans de nombreux endroits de l’application comme par exemple des composants UI : alert
, button
, accordion
…
Pour cela, on importe le composant global directement au sein du fichier 📄 main.js
. Pour l’import de composant, on utilise la PascalCase
.
import MyCounter from './components/MyCounter.vue'
createApp(App)
.component('MyCounter', MyCounter)
.mount('#app')
Après la création de l’application et avant de la monter sur #app
, il est possible de chaîner des méthodes component()
afin d’y charger des composants globalement.
La méthode component()
possède 2 paramètres :
- Nom : il s’agit de nommer le composant afin de pouvoir y faire référence depuis les templates.
- Implémentation : il s’agit d’implémenter la logique du composant au sein d’un objet JavaScript. Cette logique étant externalisée dans un SFC, on y spécifie le composant importé.
Enregistrement local
En revanche, créer des composants globaux n’est pas toujours idéal pour la simple et bonne raison qu’ils seront chargés par notre application même lorsqu’ils ne sont pas ou peu utilisés.
C’est là qu’interviennent les composants locaux, qui ne seront importés qu’au sein de composants parents qui les exploitent.
Pour enregistrer localement un composant :
- On l’importe au sein de la balise
<script>
qui s’apprête à l’utiliser, en utilisant la syntaxe des modules ES6, basée sur les mots-clésimport
etfrom
. - On expose les composants disponibles au sein d’une propriété
components
.
import MyCounter from './MyCounter.vue'
export default {
components: {
MyCounter
}
}
Si vous structurez vos composants au seins de sous-dossiers dans le dossier 📂 components
(📂 ui
, 📂 admin
…), alors je vous recommande d’utiliser le caractère @
faisant directement référence au dossier 📂 src
. Cela vous évitera des chemins relatifs complexes depuis le composant parent faisant l’import.
import SwitchUi from '@/components/ui/SwitchUi.vue'
Ici, même si le composant important localement le composant SwitchUi.vue
est situé dans un dossier 📂 components/admin/dashboard/UserCard.vue
, j’évite un import qui ressemblerait à :
import SwitchUi from '../../ui/SwitchUi.vue' // 🙃 Un peu laborieux...
Enregistrement global VS local
Mais alors ? Quand importer un composant globalement ? Quand importer un composant localement ?
Voici un tableau récapitulatif des points clés à retenir pour faire le bon choix entre importation globale et locale.
Global | Local | |
---|---|---|
Portée | Le composant est accessible dans toute l’application sans avoir besoin de l’importer dans chaque composant parent. | Le composant n’est accessible que dans le composant parent dans lequel il a été importé. |
Utilisation recommandée | Utilisation recommandée pour les grands composants réutilisables dans de nombreux endroits de l’application. | Utilisation recommandée pour les petits composants qui ne seront pas réutilisés dans d’autres parties de l’application. |
Définition | Les composants globaux doivent être définis avec la méthode component(), avant la création de l’instance Vue.js pour être accessibles dans tous les composants. | Les composants locaux peuvent être définis de manière explicite dans la section “components” du composant parent ou de manière implicite en utilisant des fichiers .vue qui contiennent un seul composant. |
Avantage | Permet une meilleure réutilisabilité et maintenabilité de code | Permet une meilleure encapsulation et modularité |
Performance | Augmente le poids du bundle final retourné au client (généré lors de la phase de build) | Optimise le poids du bundle final retourné au client (généré lors de la phase de build) |
Utiliser un composant
Appel
Le composant étant enregistré/importé, il est temps de l’exploiter depuis un autre composant. Pour cela, on pourra l’exploiter au sein de balises HTML spécifiques qui portent le nom du composant en PascalCase
.
Cette balise peut s’écrire selon les formes suivantes :
<MyComponent/>
<MyComponent>Contenu...</MyComponent>
La seconde syntaxe, constituée d’une balise ouvrante et d’une balise fermante est à privilégier lorsque le composant est voué à accueillir un contenu (nous aborderons ceci lors de l’introduction aux slots).
Notez que Vue.js prend en charge la résolution des balises kebab-case
en composants enregistrés à l’aide de PascalCase
. Cela signifie qu’un composant enregistré en tant que MyComponent
peut être référencé dans le modèle via <MyComponent>
et <my-component>
. Cela peut être intéressant dans des cas spécifiques.
<my-component/>
<my-component>Contenu...</my-component>
Indépendance des composants
Chaque composant possède son propre état. Lorsque vous utilisez un composant plusieurs fois dans votre application, chaque instance de ce composant possédera sa propre copie de l’état défini dans sa propriété data
.
<MyCounter/>
<MyCounter/>
<MyCounter/>
Cela signifie que si vous modifiez l’état d’une instance de composant, cela n’affectera pas les autres instances de ce même composant.
Cela permet à chaque instance de se comporter de manière indépendante et d’afficher des données spécifiques à son contexte.
Manipuler un composant va faire entrer en jeu de nouveaux concepts comme les props, les emits ou encore les slots.