Cours Vue.js · Module 8: Slots et composants dynamiques

Les composants dynamiques

5 min de lecture
Dans cette leçon6

Le système de transport de modules du NOVA LAB permet de brancher différents panneaux sur la même prise de la station - le panneau d'oxygène, le panneau d'eau, le panneau d'énergie. L'opérateur bascule de l'un à l'autre d'un seul bouton. Tu pourrais écrire une chaîne de v-if et de v-else-if avec une condition distincte pour chaque panneau, mais chaque nouveau panneau obligerait à modifier le template, et la chaîne grandirait sans fin. Vue propose pour cela les composants dynamiques - un élément spécial <component> avec un attribut :is, qui rend le composant vers lequel tu pointes.

Syntaxe de base

L'implémentation se fait en quatre étapes : tu importes tous les panneaux, tu crées une variable qui pointe vers le panneau actif, tu la rends avec <component :is> et tu ajoutes des boutons qui modifient cette variable. Pour stocker le composant, on utilise shallowRef, un ref qui ne réagit que lorsque la valeur entière est remplacée :

1<script setup>
2import { ref, shallowRef } from 'vue'
3import OxygenPanel from './OxygenPanel.vue'
4import WaterPanel from './WaterPanel.vue'
5import SolarPanel from './SolarPanel.vue'
6
7const tabs = { OxygenPanel, WaterPanel, SolarPanel }
8const currentTab = shallowRef(OxygenPanel)
9</script>
10
11<template>
12  <div class="station">
13    <nav>
14      <button
15        v-for="(comp, name) in tabs"
16        :key="name"
17        :class="{ active: currentTab === comp }"
18        @click="currentTab = comp"
19      >
20        {{ name }}
21      </button>
22    </nav>
23
24    <component :is="currentTab" />
25  </div>
26</template>

La boucle v-for sur l'objet tabs donne la valeur, c'est-à-dire le composant, et la clé, c'est-à-dire son nom sur le bouton. Un clic remplace currentTab, et <component> rend le nouveau panneau au même endroit ; la classe active met en évidence le bouton actif. On utilise shallowRef au lieu de ref, car on n'a pas besoin de réactivité profonde sur l'objet du composant - Vue avertirait même qu'il transforme inutilement un composant en objet réactif.

Ce qu'accepte :is

Tu peux passer à :is un objet de composant importé, comme ci-dessus, ou une chaîne : le nom d'un composant enregistré globalement avec app.component(), ou le nom d'une balise HTML ordinaire. Dans l'exemple ci-dessous, la variable isLink décide si on crée un lien ou un bouton :

1<template>
2  <!-- Composant importé -->
3  <component :is="OxygenPanel" />
4
5  <!-- Nom d'un composant enregistré globalement -->
6  <component is="WaterPanel" />
7
8  <!-- Balise HTML ordinaire -->
9  <component :is="isLink ? 'a' : 'button'">Open airlock</component>
10</template>

Dans <script setup>, les composants importés sont des variables, pas des noms enregistrés, donc là tu passes toujours l'objet via :is. Une chaîne ne fonctionne que pour les composants enregistrés globalement.

KeepAlive

Par défaut, changer de composant détruit l'ancien et en crée un nouveau. L'ordre des hooks est prévisible : l'ancien panneau reçoit onBeforeUnmount, puis onUnmounted, et le nouveau reçoit onMounted - à partir de zéro, donc un curseur déplacé ou un texte saisi sont perdus. Si tu veux conserver l'état, enveloppe le composant dans <KeepAlive> :

1<template>
2  <KeepAlive>
3    <component :is="currentTab" />
4  </KeepAlive>
5</template>

Maintenant, le composant est mis en cache - tu y reviendras avec son état préservé. L'élément du panneau quitte le document, mais l'instance avec tout son état attend en mémoire. <KeepAlive> lui-même ne rend aucun élément dans le DOM.

KeepAlive - include et exclude

Tous les panneaux ne méritent pas d'être gardés en mémoire. Les attributs include et exclude comparent le nom du composant à une liste, et max limite la taille du cache :

1<template>
2  <!-- Seulement ces composants -->
3  <KeepAlive :include="['OxygenPanel', 'WaterPanel']">
4    <component :is="currentTab" />
5  </KeepAlive>
6
7  <!-- Tous sauf ceux-ci -->
8  <KeepAlive :exclude="['HeavyModule']">
9    <component :is="currentTab" />
10  </KeepAlive>
11
12  <!-- Limite de cache (LRU) -->
13  <KeepAlive :max="5">
14    <component :is="currentTab" />
15  </KeepAlive>
16</template>

D'où un composant tient-il son nom ? Un fichier avec <script setup> le reçoit automatiquement du nom du fichier (depuis Vue 3.2.34), donc OxygenPanel.vue s'appelle OxygenPanel. La liste peut aussi être une chaîne séparée par des virgules ou une expression régulière. Avec max, le cache fonctionne comme un LRU : quand la place manque, Vue détruit l'instance utilisée le moins récemment. Un panneau hors de include se comporte comme s'il n'y avait pas de KeepAlive.

Les hooks de cycle de vie avec KeepAlive

Les composants mis en cache ont deux hooks supplémentaires, car au lieu d'être détruits, ils sont seulement détachés :

1<script setup>
2import { onActivated, onDeactivated } from 'vue'
3
4onActivated(() => {
5  console.log('Panneau activé (visible)')
6  // Rafraîchir les données, relancer les minuteurs
7})
8
9onDeactivated(() => {
10  console.log('Panneau désactivé (masqué)')
11  // Arrêter les minuteurs, sauvegarder l'état
12})
13</script>

Au premier rendu, le panneau reçoit onMounted, puis juste après onActivated, car ce hook s'exécute aussi au montage. Quand tu changes de panneau, l'ancien reçoit onDeactivated au lieu de onUnmounted, et le nouveau reçoit onActivated. Quand tu reviens, il n'y a plus de onMounted - seulement onActivated.

Props sur les composants dynamiques

Tu peux transmettre des props et écouter des événements comme avec un composant ordinaire ; ils atteignent le panneau actuellement rendu :

1<template>
2  <component
3    :is="currentTab"
4    :station="stationData"
5    @alert="handleAlert"
6  />
7</template>

Alors, quand utiliser <component :is> et quand v-if ? Deux variantes fixes se gèrent facilement avec v-if. Quand il y a beaucoup de composants et que tu veux basculer entre eux de façon dynamique, <component :is> donne un template plus propre.

Mon conseil : garde les panneaux dans un tableau d'objets { name, component } - tu généreras les boutons avec un seul v-for et tu mettras en évidence le bouton actif en comparant currentTab === tab.component. Dans l'éditeur ci-dessous, trois panneaux sont construits exactement ainsi, et KeepAlive veille à ce que le curseur du niveau d'oxygène ne revienne pas à sa valeur de départ.

Retiens : <component :is> est la prise universelle de la station - tu y branches n'importe quel panneau, et KeepAlive conserve son état pendant que tu le débranches temporairement.

Code de cette leçon : App.vue
1<script setup>
2import { shallowRef } from 'vue'
3import OxygenPanel from './OxygenPanel.vue'
4import WaterPanel from './WaterPanel.vue'
5import SolarPanel from './SolarPanel.vue'
6
7const tabs = [
8  { name: 'Oxygène', component: OxygenPanel },
9  { name: 'Eau', component: WaterPanel },
10  { name: 'Solaire', component: SolarPanel }
11]
12
13const currentTab = shallowRef(OxygenPanel)
14</script>
15
16<template>
17  <div class="nova-lab">
18    <h1>Composants dynamiques</h1>
19
20    <nav class="tabs">
21      <button
22        v-for="tab in tabs"
23        :key="tab.name"
24        :class="{ active: currentTab === tab.component }"
25        @click="currentTab = tab.component"
26      >
27        {{ tab.name }}
28      </button>
29    </nav>
30
31    <KeepAlive>
32      <component :is="currentTab" />
33    </KeepAlive>
34  </div>
35</template>
36
37<style scoped>
38.nova-lab {
39  background: #0a0e27;
40  color: #00ff88;
41  min-height: 100vh;
42  padding: 20px;
43  font-family: monospace;
44}
45h1 { color: #00ff88; margin-bottom: 20px; }
46.tabs {
47  display: flex;
48  gap: 5px;
49  margin-bottom: 15px;
50}
51.tabs button {
52  background: rgba(0, 180, 216, 0.1);
53  border: 2px solid #00b4d8;
54  color: #00b4d8;
55  padding: 10px 20px;
56  cursor: pointer;
57  font-family: monospace;
58  font-weight: bold;
59}
60.tabs button.active {
61  background: #00b4d8;
62  color: #0a0e27;
63}
64</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. Comment rend-on un composant dynamique dans Vue ?

  2. 2. Que peut-on passer à l'attribut :is dans <component> ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée un système d'onglets avec <component :is> qui bascule entre trois composants : OxygenPanel, WaterPanel, SolarPanel.

  • Tri vertical

    Ordonne les étapes de mise en place d'un composant dynamique :

  • Clique dans l'ordre

    Ordonne le code d'un composant dynamique avec des boutons :

  • Éditeur de code

    Crée un composant avec une liste d'onglets dans un tableau et rends le composant actif via <component :is>. Mets en évidence l'onglet actif.