Cours Vue.js · Module 6: Composants
Composants - Les modules de base de la construction
Dans cette leçon5
Bienvenue à la Baie d'Impression 3D de NOVA LAB ! Le Dr Nova te dit : tout comme l'imprimante 3D crée des pièces modulaires de l'habitat martien, tu vas apprendre à créer des composants Vue - des éléments d'application réutilisables.
Jusqu'ici, tout le panneau vivait dans un seul fichier App.vue. Cela suffit pour un écran, mais la base grandit : cinq cartes de modules identiques, ce sont cinq copies du même HTML, et une correction dans une carte oblige à corriger les cinq. Le composant règle ce problème - tu conçois un élément une fois et tu l'utilises autant de fois que tu veux.
Ce que tu vas apprendre
- créer des composants et les nommer selon la convention,
- les importer localement et les enregistrer globalement,
- construire une interface à partir d'un arbre de composants,
- exécuter du code dans les phases successives de la vie d'un composant,
- isoler les styles et extraire la logique commune dans des composables.
Qu'est-ce qu'un composant ?
Un composant est une unité d'interface réutilisable et autonome, avec son propre template, sa logique et ses styles - comme un module d'habitat que l'on peut connecter à d'autres. Voici une carte de module enregistrée dans le fichier ModuleCard.vue, d'abord le template et les données :
1<!-- ModuleCard.vue -->
2<template>
3 <article class="module-card" style="background: #0a0e27; border: 1px solid #00b4d8; color: #00ff88;">
4 <div class="module-icon">◆</div>
5 <h3>{{ moduleName }}</h3>
6 <p>{{ moduleType }}</p>
7 <p class="status">{{ status }}</p>
8 </article>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const moduleName = ref('Oxygen Generator Alpha')
15const moduleType = ref('Life Support')
16const status = ref('ACTIVE')
17</script>Le template affiche le nom, le type et le statut du module, et trois conteneurs ref() gardent ces données - la même syntaxe que tu utilisais dans la Launch Zone. La seule nouveauté, c'est que le fichier a son propre nom et sera utilisé ailleurs. Le boîtier de la carte s'écrit dans le même fichier, dans une section dont les styles sont limités au composant :
1<style scoped>
2.module-card {
3 border-radius: 8px;
4 padding: 1rem;
5 max-width: 300px;
6}
7
8.module-icon {
9 font-size: 3rem;
10 text-align: center;
11}
12
13.status {
14 color: #00ff88;
15 font-weight: bold;
16}
17</style>Nous rangeons les sections dans l'ordre utilisé au NOVA LAB depuis la Launch Zone : <template>, <script setup>, <style scoped>. Dans l'éditeur, tu verras aussi des fichiers avec <script setup> en haut - c'est correct aussi, car Vue exige seulement la cohérence dans un projet et le style en dernier.
Utiliser un composant
Pour imprimer la carte dans la base, on importe le fichier dans App.vue et on utilise le nom de l'import comme une balise HTML :
1<!-- App.vue -->
2<template>
3 <div class="production-bay" style="background: #0a0e27;">
4 <ModuleCard />
5 <ModuleCard />
6 <ModuleCard />
7 </div>
8</template>
9
10<script setup>
11import ModuleCard from './components/ModuleCard.vue'
12</script>Chaque balise <ModuleCard /> crée une instance séparée du composant avec ses propres données, donc changer le statut d'une carte ne touchera pas les autres. Le fichier ModuleCard.vue n'a pas changé et ne changera pas, quel que soit le nombre de copies. Pour l'instant, les trois cartes affichent les mêmes données - transmettre des valeurs différentes à chaque carte, ce qu'on appelle les props, tu l'apprendras à la Station de Communication. C'est le principal avantage d'une architecture par composants : tu écris le code une fois, tu le réutilises souvent, et tu corriges à un seul endroit.
Nommage des composants
On écrit les noms de composants en PascalCase, c'est-à-dire chaque mot avec une majuscule, sans espaces ni tirets bas. On nomme les fichiers de la même façon : ModuleCard.vue, et non module_card.vue ou moduleCard.vue :
1<script setup>
2// PascalCase - recommandé
3import ArtworkCard from './ArtworkCard.vue'
4import UserProfile from './UserProfile.vue'
5import NavigationMenu from './NavigationMenu.vue'
6
7// Dans le template - PascalCase ou kebab-case
8</script>
9
10<template>
11 <!-- PascalCase -->
12 <ArtworkCard />
13 <UserProfile />
14
15 <!-- ou kebab-case -->
16 <artwork-card />
17 <user-profile />
18</template>Les deux formes de balise fonctionnent dans les templates des fichiers .vue, mais je te recommande PascalCase - on voit tout de suite que <UserProfile /> est un composant et <div> du HTML ordinaire. Une deuxième règle du guide de style officiel dit que le nom d'un composant doit compter au moins deux mots. Tous les éléments HTML ont des noms d'un seul mot, donc un composant Card pourrait entrer en collision avec une future balise du navigateur, alors que ModuleCard ne le fera jamais. L'exception est le composant racine App, et les composants de base reçoivent un préfixe, par exemple BaseButton.
Organisation des fichiers
Quand les composants se multiplient, on les regroupe dans des sous-dossiers de src/components/ selon le rôle qu'ils jouent dans la base :
1src/
2├── components/
3│ ├── common/
4│ │ ├── BaseButton.vue
5│ │ ├── BasePanelCard.vue
6│ │ └── BaseInput.vue
7│ ├── modules/
8│ │ ├── ModuleCard.vue
9│ │ ├── ModuleList.vue
10│ │ └── ModuleDetail.vue
11│ └── layout/
12│ ├── MissionHeader.vue
13│ ├── ControlFooter.vue
14│ └── NavSidebar.vueLe dossier common/ contient les composants de base, modules/ les cartes et listes de modules, et layout/ les éléments du cadre de l'écran. Les composants réutilisables vont toujours dans src/components/, tandis que les écrans entiers reliés à la navigation vont dans le dossier src/views/ que tu connais depuis la structure du projet.
Dans l'éditeur ci-dessous, App.vue utilise trois fois le composant PrinterStatusPanel. Tu y verras defineProps et defineEmits - ce sont les canaux de communication de la Station de Communication, et pour aujourd'hui il suffit de savoir que le panneau est un fichier séparé, importé et utilisé comme une balise.
Retiens ceci : un composant est un module d'habitat imprimé - tu le conçois une fois, dans un fichier au nom de plusieurs mots en PascalCase, et tu le montes dans la base autant de fois qu'il le faut.
Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3import PrinterStatusPanel from './PrinterStatusPanel.vue'
4
5const printers = ref([
6 {
7 id: 'p1',
8 name: '3D Printer Alpha',
9 status: 'printing',
10 progress: 67,
11 material: 'PLA Composite',
12 temperature: 210
13 },
14 {
15 id: 'p2',
16 name: '3D Printer Beta',
17 status: 'idle',
18 progress: 0,
19 material: 'Carbon Fiber',
20 temperature: 25
21 },
22 {
23 id: 'p3',
24 name: '3D Printer Gamma',
25 status: 'error',
26 progress: 45,
27 material: 'Titanium Alloy',
28 temperature: 185
29 }
30])
31
32function handlePrinterAction(printerId, action) {
33 const printer = printers.value.find(p => p.id === printerId)
34 if (printer) {
35 console.log(`Action ${action} sur ${printer.name}`)
36 if (action === 'start') {
37 printer.status = 'printing'
38 } else if (action === 'stop') {
39 printer.status = 'idle'
40 printer.progress = 0
41 } else if (action === 'pause') {
42 printer.status = 'paused'
43 }
44 }
45}
46</script>
47
48<template>
49 <div class="printing-lab">
50 <div class="header">
51 <h1>Imprimerie 3D - NOVA LAB</h1>
52 <p class="subtitle">Panneau de contrôle des imprimantes 3D sur Mars</p>
53 </div>
54
55 <div class="info-panel">
56 <h3>Définir des composants avec props et emits</h3>
57 <p>Les composants reçoivent des données via les <strong>props</strong> et communiquent via les <strong>emits</strong></p>
58 </div>
59
60 <div class="printers-grid">
61 <PrinterStatusPanel
62 v-for="printer in printers"
63 :key="printer.id"
64 :printer-id="printer.id"
65 :printer-name="printer.name"
66 :status="printer.status"
67 :progress="printer.progress"
68 :material="printer.material"
69 :temperature="printer.temperature"
70 @action="handlePrinterAction"
71 />
72 </div>
73 </div>
74</template>
75
76<style scoped>
77.printing-lab {
78 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
79 min-height: 100vh;
80 padding: 2rem;
81 color: #ffffff;
82 font-family: 'Courier New', monospace;
83}
84
85.header {
86 text-align: center;
87 margin-bottom: 2rem;
88 padding-bottom: 1rem;
89 border-bottom: 2px solid #00ff88;
90}
91
92.header h1 {
93 font-size: 2.5rem;
94 color: #00ff88;
95 text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
96 margin: 0;
97}
98
99.subtitle {
100 color: #00b4d8;
101 font-size: 1.2rem;
102 margin-top: 0.5rem;
103}
104
105.info-panel {
106 background: rgba(0, 180, 216, 0.1);
107 border: 2px solid #00b4d8;
108 border-radius: 10px;
109 padding: 1.5rem;
110 margin-bottom: 2rem;
111}
112
113.info-panel h3 {
114 color: #00b4d8;
115 margin-top: 0;
116}
117
118.info-panel strong {
119 color: #00ff88;
120}
121
122.printers-grid {
123 display: grid;
124 grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
125 gap: 1.5rem;
126}
127</style>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Qu'est-ce qu'un composant en Vue.js ?
2. Quel est le principal avantage de l'architecture par composants en Vue ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Crée un composant MarsModule.vue avec les sections template, script setup et style scoped.
- Clique dans l'ordre
Range les sections d'un Single File Component dans l'ordre utilisé au NOVA LAB :
- Éditeur de code
Crée un composant StatusPanel.vue avec ref() pour le statut et la température affichés dans le template.