Cours Vue.js · Module 2: Syntaxe des templates
Directives conditionnelles
Dans cette leçon4
Tout comme les systèmes du NOVA LAB prennent des décisions à partir des relevés des capteurs, Vue te permet de contrôler ce qui apparaît dans l'interface.
Les classes de la leçon précédente changent l'apparence d'un élément, mais l'élément existe toujours. L'hologramme de la station a parfois besoin de quelque chose de plus fort : n'afficher que le panneau du système choisi par l'opérateur, et ne montrer le message de panne que pendant une panne. C'est le rôle des directives conditionnelles - v-if, qui décide si l'élément sera créé tout court, et v-show, qui se contente de le masquer.
v-if, v-else-if, v-else
v-if ne rend un élément que si l'expression est vraie. v-else-if ajoute d'autres conditions, et v-else, sans aucune valeur, attrape tout le reste. Les deux doivent suivre immédiatement un élément portant v-if ou v-else-if. Une balise <template> avec v-if permet de placer tout un groupe d'éléments sous une même condition :
1<template>
2 <div class="system-monitor" style="background: #0a0e27; color: #00ff88;">
3 <div v-if="systemType === 'oxygen'">
4 Générateur d'oxygène - Système actif
5 </div>
6 <div v-else-if="systemType === 'water'">
7 Recyclage de l'eau - Système actif
8 </div>
9 <div v-else-if="systemType === 'power'">
10 Réacteur à plasma - Système actif
11 </div>
12 <div v-else>
13 Système inconnu
14 </div>
15
16 <!-- Sur un groupe d'éléments -->
17 <template v-if="showDetails">
18 <h2 style="color: #00b4d8;">{{ module.name }}</h2>
19 <p>{{ module.description }}</p>
20 <span>Status: {{ module.status }}</span>
21 </template>
22 </div>
23</template>Avec systemType égal à 'oxygen', seul le premier div existe dans le DOM. Les autres ne sont pas cachés - ils n'y sont tout simplement pas. Le <template> lui-même n'est pas rendu non plus : le titre, le paragraphe et le span arrivent sur la page sans enveloppe supplémentaire. Si un autre élément se glissait entre v-if et v-else, le compilateur signalerait que v-else n'a pas de v-if adjacent.
Les conditions lisent des données ordinaires du script :
1<script setup>
2import { ref, reactive } from 'vue'
3
4const systemType = ref('oxygen')
5const showDetails = ref(true)
6
7const module = reactive({
8 name: 'Life Support Alpha',
9 description: 'Primary oxygen generation system',
10 status: 'ONLINE'
11})
12</script>Règle systemType.value = 'water', et Vue retire l'élément de l'oxygène puis crée celui de l'eau. À chaque rendu, Vue construit un nouvel arbre de Virtual DOM, c'est-à-dire une description légère de la page en JavaScript. Quand la condition est fausse, l'élément manque dans cet arbre. Vue compare le nouvel arbre avec le précédent et seulement ensuite ajoute ou retire le véritable élément dans le navigateur. v-if est en plus paresseux : si la condition est fausse au départ, rien n'est rendu, et à chaque bascule le contenu du bloc est détruit puis recréé de zéro.
v-show
v-show fonctionne autrement : l'élément est toujours rendu, et la directive ne fait que basculer la propriété CSS display.
1<template>
2 <div class="alert-panel">
3 <!-- v-show - toujours rendu, juste du CSS display: none -->
4 <div v-show="isVisible" class="alert-message"
5 style="background: #ff4444; color: white; padding: 1rem;">
6 ALERTE CRITIQUE : les niveaux d'oxygène chutent !
7 </div>
8 </div>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const isVisible = ref(true)
15</script>Avec isVisible à false, l'élément reçoit style="display: none;", mais il reste dans le DOM avec son état. v-show ne fonctionne pas sur une balise <template> et ne s'associe pas à v-else. Le plus souvent, il est piloté par un bouton qui inverse une valeur booléenne :
1<template>
2 <button @click="isVisible = !isVisible">Basculer le panneau</button>
3 <div v-show="isVisible">Panneau de diagnostic</div>
4</template>Chaque clic transforme true en false et inversement. C'est la même instruction dans le template que celle que tu as vue avec v-on.
v-if vs v-show
Les deux directives produisent le même effet à l'écran, mais elles coûtent des choses différentes :
| Caractéristique | v-if | v-show |
|---|---|---|
| Rendu | Conditionnel | Toujours |
| Coût initial | Faible | Plus élevé |
| Coût de bascule | Plus élevé | Faible |
| Cas d'usage | Changements rares | Bascules fréquentes (ex. alertes) |
La documentation de Vue donne le même conseil que moi : v-show pour ce qui bascule très souvent, comme les alertes ou les onglets, et v-if quand la condition change rarement ou que le contenu est lourd et ne mérite pas d'être construit à l'avance.
Exemple pratique
Le panneau de mission combine deux chaînes de conditions : un filtre des systèmes et l'état du chargement des données. La première partie du template contient les boutons du filtre :
1<template>
2 <div class="mission-dashboard" style="background: #0a0e27; padding: 1rem;">
3 <div class="system-filter">
4 <button @click="filter = 'all'" class="btn-filter">All</button>
5 <button @click="filter = 'life-support'" class="btn-filter">Life Support</button>
6 <button @click="filter = 'power'" class="btn-filter">Power</button>
7 </div>
8
9 <div v-if="filter === 'all'" class="systems-grid">
10 Tous les systèmes...
11 </div>
12 <div v-else-if="filter === 'life-support'" class="systems-grid">
13 Systèmes de survie uniquement...
14 </div>
15 <div v-else class="systems-grid">
16 Systèmes d'alimentation uniquement...
17 </div>Chaque bouton affecte une nouvelle valeur à filter, et la chaîne affiche exactement une grille. La seconde partie du même template gère le chargement des données des capteurs :
1 <!-- État de chargement -->
2 <div v-if="isLoading" class="loader" style="color: #00b4d8;">
3 Chargement des données des capteurs...
4 </div>
5 <div v-else-if="error" class="error" style="color: #ff4444;">
6 {{ error }}
7 </div>
8 <div v-else class="content" style="color: #00ff88;">
9 {{ data }}
10 </div>
11 </div>
12</template>L'ordre compte : d'abord le chargement, ensuite l'erreur, et les données en dernier. Quatre refs pilotent l'ensemble :
1<script setup>
2import { ref } from 'vue'
3
4const filter = ref('all')
5const isLoading = ref(false)
6const error = ref(null)
7const data = ref('All systems nominal')
8</script>Règle isLoading.value = true, et le message de chargement apparaît à la place des données. Dans la prochaine leçon, tu verras v-for et tu comprendras pourquoi on ne le combine pas avec v-if sur le même élément.
Retiens : v-if ouvre et ferme l'écoutille du module, donc l'élément disparaît du DOM, alors que v-show tire seulement un rideau derrière lequel l'élément continue d'attendre.
Code de cette leçon : App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4const oxygenLevel = ref(95)
5const temperature = ref(-55)
6const solarStorm = ref(false)
7const crewStatus = ref('all-safe')
8const showWarnings = ref(true)
9
10const alertType = computed(() => {
11 if (oxygenLevel.value < 50) return 'critical'
12 if (oxygenLevel.value < 80) return 'warning'
13 return 'safe'
14})
15
16function simulateStorm() {
17 solarStorm.value = true
18 oxygenLevel.value = 45
19 temperature.value = -80
20 crewStatus.value = 'emergency'
21}
22
23function normalizeConditions() {
24 solarStorm.value = false
25 oxygenLevel.value = 95
26 temperature.value = -55
27 crewStatus.value = 'all-safe'
28}
29
30function adjustOxygen(value) {
31 oxygenLevel.value = Math.max(0, Math.min(100, oxygenLevel.value + value))
32}
33</script>
34
35<template>
36 <div class="conditions-monitor">
37 <h1>Moniteur de conditions - Base martienne NOVA</h1>
38
39 <!-- v-if, v-else-if, v-else -->
40 <div class="alert-section">
41 <div v-if="alertType === 'critical'" class="alert alert-critical">
42 ALARME CRITIQUE ! Niveau d'oxygène sous le seuil de sécurité !
43 </div>
44 <div v-else-if="alertType === 'warning'" class="alert alert-warning">
45 AVERTISSEMENT : le niveau d'oxygène est passé sous la normale.
46 </div>
47 <div v-else class="alert alert-safe">
48 Tous les systèmes fonctionnent normalement.
49 </div>
50 </div>
51
52 <!-- v-show vs v-if -->
53 <div class="info-panel">
54 <h2>Différence entre v-if et v-show</h2>
55 <button @click="showWarnings = !showWarnings" class="btn btn-toggle">
56 Basculer les avertissements (v-show)
57 </button>
58
59 <!-- v-show : l'élément reste dans le DOM, seulement masqué via CSS -->
60 <div v-show="showWarnings" class="warning-box">
61 <p>Cet avertissement est contrôlé par v-show</p>
62 <p>L'élément reste dans le DOM, mais est masqué via display: none</p>
63 </div>
64
65 <!-- v-if : l'élément est retiré/ajouté du DOM -->
66 <div v-if="showWarnings" class="warning-box">
67 <p>Cet avertissement est contrôlé par v-if</p>
68 <p>L'élément est complètement retiré/ajouté du DOM</p>
69 </div>
70 </div>
71
72 <div class="status-grid">
73 <!-- Rendu conditionnel du statut de la tempête -->
74 <div class="status-card" :class="{ 'storm-active': solarStorm }">
75 <h3>Tempête solaire</h3>
76 <div v-if="solarStorm" class="storm-warning">
77 <p class="storm-icon">!!!</p>
78 <p>TEMPÊTE SOLAIRE ACTIVE</p>
79 <p>Toutes les opérations extérieures suspendues</p>
80 </div>
81 <div v-else class="storm-calm">
82 <p class="calm-icon">OK</p>
83 <p>Conditions stables</p>
84 </div>
85 </div>
86
87 <!-- Statut de l'équipage avec plusieurs conditions -->
88 <div class="status-card">
89 <h3>Statut de l'équipage</h3>
90 <div v-if="crewStatus === 'emergency'" class="crew-emergency">
91 SITUATION D'URGENCE - Tous à l'abri
92 </div>
93 <div v-else-if="crewStatus === 'alert'" class="crew-alert">
94 État d'alerte élevé
95 </div>
96 <div v-else-if="crewStatus === 'all-safe'" class="crew-safe">
97 Tout l'équipage est en sécurité - Opérations de routine
98 </div>
99 <div v-else class="crew-unknown">
100 Statut inconnu
101 </div>
102 </div>
103
104 <!-- Contrôle du niveau d'oxygène -->
105 <div class="status-card">
106 <h3>Niveau d'oxygène</h3>
107 <div class="oxygen-display" :class="'oxygen-' + alertType">
108 {{ oxygenLevel }}%
109 </div>
110 <div class="oxygen-controls">
111 <button @click="adjustOxygen(-10)" class="btn btn-small">-10%</button>
112 <button @click="adjustOxygen(10)" class="btn btn-small">+10%</button>
113 </div>
114 </div>
115
116 <!-- Température -->
117 <div class="status-card">
118 <h3>Température</h3>
119 <div class="temp-display">{{ temperature }}°C</div>
120 <div v-if="temperature < -70" class="temp-warning">
121 Températures extrêmement basses
122 </div>
123 </div>
124 </div>
125
126 <div class="controls-section">
127 <h2>Simulation des conditions</h2>
128 <div class="button-group">
129 <button @click="simulateStorm" class="btn btn-danger">
130 Simuler une tempête solaire
131 </button>
132 <button @click="normalizeConditions" class="btn btn-success">
133 Rétablir les conditions normales
134 </button>
135 </div>
136 </div>
137
138 <div class="info-box">
139 <h3>Le rendu conditionnel dans Vue</h3>
140 <ul>
141 <li><strong>v-if</strong> - Rend l'élément uniquement quand la condition est vraie (retire du DOM)</li>
142 <li><strong>v-else-if</strong> - Conditions supplémentaires dans la chaîne</li>
143 <li><strong>v-else</strong> - Cas par défaut</li>
144 <li><strong>v-show</strong> - Toujours dans le DOM, masque via CSS (meilleur pour les changements fréquents)</li>
145 </ul>
146 </div>
147 </div>
148</template>
149
150<style scoped>
151.conditions-monitor {
152 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
153 min-height: 100vh;
154 padding: 2rem;
155 color: #ffffff;
156 font-family: 'Courier New', monospace;
157}
158
159h1 {
160 text-align: center;
161 color: #00ff88;
162 text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
163 margin-bottom: 2rem;
164}
165
166.alert-section {
167 margin-bottom: 2rem;
168}
169
170.alert {
171 padding: 1.5rem;
172 border-radius: 10px;
173 font-size: 1.2rem;
174 font-weight: bold;
175 text-align: center;
176 animation: slideIn 0.5s ease;
177}
178
179@keyframes slideIn {
180 from { transform: translateY(-20px); opacity: 0; }
181 to { transform: translateY(0); opacity: 1; }
182}
183
184.alert-critical {
185 background: rgba(255, 0, 85, 0.2);
186 border: 3px solid #ff0055;
187 animation: pulse 1s infinite;
188}
189
190.alert-warning {
191 background: rgba(255, 180, 0, 0.2);
192 border: 3px solid #ffb400;
193}
194
195.alert-safe {
196 background: rgba(0, 255, 136, 0.2);
197 border: 3px solid #00ff88;
198}
199
200.info-panel {
201 background: rgba(0, 180, 216, 0.1);
202 border: 2px solid #00b4d8;
203 border-radius: 10px;
204 padding: 1.5rem;
205 margin-bottom: 2rem;
206}
207
208.warning-box {
209 background: rgba(255, 180, 0, 0.1);
210 border: 2px solid #ffb400;
211 border-radius: 5px;
212 padding: 1rem;
213 margin-top: 1rem;
214}
215
216.status-grid {
217 display: grid;
218 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
219 gap: 1.5rem;
220 margin-bottom: 2rem;
221}
222
223.status-card {
224 background: rgba(0, 255, 136, 0.1);
225 border: 2px solid #00ff88;
226 border-radius: 10px;
227 padding: 1.5rem;
228 transition: all 0.3s ease;
229}
230
231.status-card.storm-active {
232 border-color: #ff0055;
233 background: rgba(255, 0, 85, 0.2);
234 animation: shake 0.5s infinite;
235}
236
237@keyframes shake {
238 0%, 100% { transform: translateX(0); }
239 25% { transform: translateX(-5px); }
240 75% { transform: translateX(5px); }
241}
242
243.status-card h3 {
244 color: #00b4d8;
245 margin-top: 0;
246}
247
248.storm-warning, .storm-calm {
249 text-align: center;
250}
251
252.storm-icon {
253 font-size: 3rem;
254 margin: 0;
255 animation: flicker 0.3s infinite;
256}
257
258@keyframes flicker {
259 0%, 100% { opacity: 1; }
260 50% { opacity: 0.5; }
261}
262
263.calm-icon {
264 font-size: 3rem;
265 margin: 0;
266}
267
268.crew-emergency {
269 color: #ff0055;
270 font-weight: bold;
271 padding: 1rem;
272 background: rgba(255, 0, 85, 0.2);
273 border-radius: 5px;
274}
275
276.crew-alert {
277 color: #ffb400;
278 font-weight: bold;
279}
280
281.crew-safe {
282 color: #00ff88;
283 font-weight: bold;
284}
285
286.oxygen-display {
287 font-size: 3rem;
288 text-align: center;
289 font-weight: bold;
290 margin: 1rem 0;
291}
292
293.oxygen-critical {
294 color: #ff0055;
295 animation: pulse 1s infinite;
296}
297
298.oxygen-warning {
299 color: #ffb400;
300}
301
302.oxygen-safe {
303 color: #00ff88;
304}
305
306.oxygen-controls {
307 display: flex;
308 gap: 1rem;
309 justify-content: center;
310}
311
312.temp-display {
313 font-size: 2.5rem;
314 text-align: center;
315 color: #00b4d8;
316 margin: 1rem 0;
317}
318
319.temp-warning {
320 color: #00b4d8;
321 text-align: center;
322 font-size: 0.9rem;
323}
324
325.controls-section {
326 background: rgba(255, 255, 255, 0.05);
327 border: 2px solid #00ff88;
328 border-radius: 10px;
329 padding: 1.5rem;
330 margin-bottom: 2rem;
331}
332
333.button-group {
334 display: flex;
335 gap: 1rem;
336 justify-content: center;
337 flex-wrap: wrap;
338}
339
340.btn {
341 padding: 1rem 2rem;
342 border: none;
343 border-radius: 5px;
344 font-size: 1rem;
345 font-weight: bold;
346 cursor: pointer;
347 transition: all 0.3s ease;
348 font-family: 'Courier New', monospace;
349}
350
351.btn-toggle {
352 background: #00b4d8;
353 color: #ffffff;
354}
355
356.btn-danger {
357 background: #ff0055;
358 color: #ffffff;
359}
360
361.btn-success {
362 background: #00ff88;
363 color: #0a0e27;
364}
365
366.btn-small {
367 padding: 0.5rem 1rem;
368 font-size: 0.9rem;
369 background: #00b4d8;
370 color: #ffffff;
371}
372
373.btn:hover {
374 transform: scale(1.05);
375 box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
376}
377
378.info-box {
379 background: rgba(0, 255, 136, 0.05);
380 border: 2px solid #00ff88;
381 border-radius: 10px;
382 padding: 1.5rem;
383}
384
385.info-box h3 {
386 color: #00ff88;
387 margin-top: 0;
388}
389
390.info-box ul {
391 list-style: none;
392 padding: 0;
393}
394
395.info-box li {
396 padding: 0.5rem 0;
397 color: #ffffff;
398 border-left: 3px solid #00b4d8;
399 padding-left: 1rem;
400 margin: 0.5rem 0;
401}
402
403.info-box strong {
404 color: #00b4d8;
405}
406</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. Quelle est la différence entre v-if et v-show ?
2. Quand est-il préférable d'utiliser v-show plutôt que v-if ?
3. Peux-tu utiliser v-else-if dans Vue ?
4. Quel extrait de code utilise correctement v-if avec v-else ?
5. Qu'arrive-t-il à un élément du DOM lorsque la condition v-if passe à false ?
Tâches pratiques dans le jeu
- Éditeur de code
Utilise v-if, v-else-if et v-else pour le rendu conditionnel des statuts système.
- Tri vertical
Ordonne la chaîne conditionnelle correcte de v-if à v-else :
- Tri vertical
Ordonne les étapes de v-if, de la vérification de la condition au rendu :
- Clique dans l'ordre
Ordonne la syntaxe : d'abord le bouton de bascule, puis l'élément avec v-show en dessous :
- Éditeur de code
Utilise v-show avec un bouton de bascule pour masquer/afficher des sections.