Cours Vue.js · Module 2: Syntaxe des templates

Directives conditionnelles

5 min de lecture
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éristiquev-ifv-show
RenduConditionnelToujours
Coût initialFaiblePlus élevé
Coût de basculePlus élevéFaible
Cas d'usageChangements raresBascules 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. 1. Quelle est la différence entre v-if et v-show ?

  2. 2. Quand est-il préférable d'utiliser v-show plutôt que v-if ?

  3. 3. Peux-tu utiliser v-else-if dans Vue ?

  4. 4. Quel extrait de code utilise correctement v-if avec v-else ?

  5. 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.