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

Projet : Système de stockage du NOVA LAB

5 min de lecture
Dans cette leçon4

Il est temps d'appliquer tous les patterns de stockage dans un seul projet ! Dr. Nova te confie le panneau de gestion de la soute à fret de la station. Les exigences semblent simples, mais chacune correspond à un mécanisme différent : le panneau doit afficher trois vues (la liste des modules, le formulaire d'ajout et le journal), le formulaire ne doit pas perdre les données saisies quand tu changes de vue, et une alarme concernant un module endommagé doit surgir par-dessus tout le reste. Tu connais déjà chacun de ces outils séparément. Dans ce projet, tu vas voir comment ils collaborent.

Le code dans l'éditeur utilise :

  • les slots nommés - une disposition flexible du panneau de fret avec les sections header, default et footer,
  • les composants dynamiques - basculer entre les vues de la station,
  • KeepAlive - préserver l'état des formulaires,
  • Teleport - une modale d'alerte critique rendue dans <body>,
  • un événement personnalisé alert, qui relie la liste du fret à la modale.

Tu ajouteras toi-même les scoped slots pour le rendu de la liste des modules, ainsi que les composants asynchrones, en enrichissant le projet.

Architecture

Avant d'ouvrir les fichiers, observe l'arbre des composants. App.vue remplit les slots du panneau StationDashboard et échange les vues dans sa section principale ; la modale d'alarme se trouve à côté, en dehors du panneau :

1App
2  ├── StationDashboard (slots nommés : header, default, footer)
3  │     ├── #header → Onglets de navigation
4  │     ├── #default → <component :is> + KeepAlive
5  │     │     ├── CargoList (liste des modules, émet alert)
6  │     │     ├── AddModule (formulaire avec état local)
7  │     │     └── StationLog (journal d'événements)
8  │     └── #footer → barre d'état de la station
9  └── Teleport → modale d'alerte (rendue dans body)

StationDashboard ne sait rien du fret : il reçoit seulement un en-tête avec les onglets, le contenu principal et un pied de page. L'en-tête et le pied de page sont enveloppés dans un v-if qui vérifie slots.header et slots.footer, et le slot par défaut a pour fallback "No panel selected".

Le parcours de l'alarme

Le plus intéressant, c'est le chemin que parcourt l'alarme. La section principale du panneau est un <component :is> enveloppé dans KeepAlive, avec un écouteur de l'événement alert :

1<template #default>
2  <KeepAlive>
3    <component
4      :is="currentTab"
5      @alert="triggerAlert"
6    />
7  </KeepAlive>
8</template>

L'écouteur atteint la vue actuellement rendue. CargoList déclare l'événement avec defineEmits(['alert']) et l'émet quand tu cliques sur "Inspect" à côté d'un module au statut damaged. La fonction triggerAlert dans App.vue enregistre le message dans alertMsg et met showAlert à true. C'est cet indicateur que lit la modale :

1<Teleport to="body">
2  <div v-if="showAlert" class="modal-overlay" @click.self="showAlert = false">
3    <div class="modal">
4      <h3>STATION ALERT</h3>
5      <p>{{ alertMsg }}</p>
6      <button @click="showAlert = false">Dismiss</button>
7    </div>
8  </div>
9</Teleport>

La modale est rendue dans <body>, donc aucun cadre de panneau ne la rognera, tandis que son état appartient toujours à App.vue. Le modificateur .self fait qu'un clic sur le fond semi-transparent ferme la fenêtre, mais pas un clic à l'intérieur du cadre de l'alarme.

Tâche

Analyse le code dans l'éditeur et comprends comment tous les patterns fonctionnent ensemble :

  1. StationDashboard - un composant avec des slots nommés (header, default, footer) qui crée une mise en page,
  2. CargoList - une liste de modules de fret qui émet alert pour un module endommagé,
  3. Composants dynamiques - <component :is> bascule entre CargoList, AddModule et StationLog,
  4. KeepAlive - préserve l'état de AddModule après un changement d'onglet,
  5. Teleport - la modale d'alerte rendue dans body, déclenchée depuis n'importe quel panneau.

Observations clés : les slots nommés dans StationDashboard fournissent une mise en page flexible sans structure figée, KeepAlive évite la perte des données du formulaire dans AddModule, et Teleport garantit un positionnement correct de la modale. Vérifie-le toi-même : saisis un nom de module dans le formulaire, passe au journal et reviens - le texte sera toujours là.

Pour aller plus loin

Une fois le code existant compris, enrichis-le avec les patterns qu'il ne contient pas encore :

  1. Dans CargoList, enveloppe le balisage de la ligne dans <slot :item="item">, en gardant l'apparence actuelle comme fallback. Le scoped slot permettra alors au parent de décider comment rendre chaque module de fret.
  2. Remplace l'import de StationLog par defineAsyncComponent, pour que le journal ne se charge qu'à sa première ouverture.
  3. Ajoute à KeepAlive l'attribut :include="['AddModule']" et vérifie que le cache ne couvre plus que le formulaire.

Mon conseil : enrichis le projet pas à pas et, après chaque modification, change d'onglet et déclenche l'alarme, car chacun de ces mécanismes casse d'une manière différente. Tu enverras le système terminé à ton mentor dans la tâche suivante, avec une courte description de l'architecture.

Retiens : une bonne soute est une équipe de patterns bien coordonnée - les slots construisent le cadre, les composants dynamiques échangent la cargaison, KeepAlive garde l'état, et Teleport envoie l'alarme là où tout l'équipage la verra.

Code de cette leçon : App.vue
1<script setup>
2import { ref, shallowRef } from 'vue'
3import StationDashboard from './StationDashboard.vue'
4import CargoList from './CargoList.vue'
5import AddModule from './AddModule.vue'
6import StationLog from './StationLog.vue'
7
8const tabs = [
9  { name: 'Fret', component: CargoList },
10  { name: 'Ajouter un module', component: AddModule },
11  { name: 'Journal', component: StationLog }
12]
13
14const currentTab = shallowRef(CargoList)
15const showAlert = ref(false)
16const alertMsg = ref('')
17
18const triggerAlert = (msg) => {
19  alertMsg.value = msg
20  showAlert.value = true
21}
22</script>
23
24<template>
25  <div class="nova-lab">
26    <StationDashboard>
27      <template #header>
28        <h1>Système de fret du NOVA LAB</h1>
29        <nav class="tabs">
30          <button
31            v-for="tab in tabs"
32            :key="tab.name"
33            :class="{ active: currentTab === tab.component }"
34            @click="currentTab = tab.component"
35          >
36            {{ tab.name }}
37          </button>
38        </nav>
39      </template>
40
41      <template #default>
42        <KeepAlive>
43          <component
44            :is="currentTab"
45            @alert="triggerAlert"
46          />
47        </KeepAlive>
48      </template>
49
50      <template #footer>
51        <p>NOVA LAB v2087.3 | Modules : 4 | Statut : Opérationnel</p>
52      </template>
53    </StationDashboard>
54
55    <Teleport to="body">
56      <div v-if="showAlert" class="modal-overlay" @click.self="showAlert = false">
57        <div class="modal">
58          <h3>ALERTE STATION</h3>
59          <p>{{ alertMsg }}</p>
60          <button @click="showAlert = false">Ignorer</button>
61        </div>
62      </div>
63    </Teleport>
64  </div>
65</template>
66
67<style scoped>
68.nova-lab {
69  background: #0a0e27;
70  color: #00ff88;
71  min-height: 100vh;
72  padding: 20px;
73  font-family: monospace;
74}
75h1 { color: #00ff88; margin: 0; font-size: 18px; }
76.tabs { display: flex; gap: 5px; margin-top: 10px; }
77.tabs button {
78  background: rgba(0, 180, 216, 0.1);
79  border: 1px solid #00b4d8;
80  color: #00b4d8;
81  padding: 6px 14px;
82  cursor: pointer;
83  font-family: monospace;
84}
85.tabs button.active {
86  background: #00b4d8;
87  color: #0a0e27;
88}
89</style>
90
91<style>
92.modal-overlay {
93  position: fixed;
94  inset: 0;
95  background: rgba(0,0,0,0.8);
96  display: flex;
97  align-items: center;
98  justify-content: center;
99  z-index: 9999;
100}
101.modal {
102  background: #1a1a2e;
103  border: 3px solid #ff4444;
104  padding: 30px;
105  max-width: 400px;
106  text-align: center;
107  color: #00ff88;
108  font-family: monospace;
109}
110.modal h3 { color: #ff4444; margin-bottom: 15px; letter-spacing: 2px; }
111.modal button {
112  margin-top: 15px;
113  background: #ff4444;
114  color: white;
115  border: none;
116  padding: 8px 20px;
117  cursor: pointer;
118  font-family: monospace;
119}
120</style>

Tu as repéré une erreur dans cette leçon ?