Cours Vue.js · Module 8: Slots et composants dynamiques
Les scoped slots
Dans cette leçon5
Au NOVA LAB, le système d'inventaire fournit des données sur chaque module de fret, mais c'est l'opérateur qui décide comment les afficher - en liste, en tableau ou en cartes. Les scoped slots sont le mécanisme qui transmet des données de l'enfant vers le contenu du slot écrit par le parent.
Le problème
Les slots ordinaires donnent au parent le contrôle de l'apparence, mais que faire si l'enfant possède des données dont le parent a besoin ? Par exemple, l'enfant parcourt une liste de modules et veut confier au parent le rendu de chaque élément. Tu sais depuis les leçons précédentes que le contenu du slot est compilé dans la portée du parent, donc la variable item de la boucle v-for à l'intérieur de l'enfant lui est invisible. Il te faut un câble de données qui traverse le port d'amarrage dans l'autre sens.
Transmettre des données par un slot
L'enfant transmet les données sous forme d'attributs sur la balise <slot>, exactement comme on transmet des props aux composants. Ces valeurs s'appellent les props du slot (slot props). Le ModuleList ci-dessous rend le slot à chaque tour de boucle avec l'élément courant et son numéro :
1<!-- ModuleList.vue (enfant) -->
2<script setup>
3import { ref } from 'vue'
4const items = ref([
5 { id: 1, name: 'Oxygen Tank', status: 'loaded' },
6 { id: 2, name: 'Water Filter', status: 'pending' },
7 { id: 3, name: 'Solar Cell', status: 'damaged' }
8])
9</script>
10
11<template>
12 <div class="module-list">
13 <div v-for="(item, index) in items" :key="item.id">
14 <slot :item="item" :index="index">
15 <!-- Fallback : rendu simple -->
16 <p>{{ item.name }}</p>
17 </slot>
18 </div>
19 </div>
20</template>Le slot est rendu trois fois, chaque fois avec les données d'un autre module. Le fallback fonctionne toujours comme dans la première leçon : un parent qui ne fournit pas de contenu obtient une simple liste de noms.
Recevoir les données dans le parent
Le parent reçoit les props du slot avec la directive v-slot et un nom de variable. Vue transmet toutes les props du slot dans un seul objet, ici appelé slotProps, et comme la valeur de v-slot se comporte comme un paramètre de fonction, tu peux la déstructurer tout de suite :
1<template>
2 <!-- Syntaxe complète -->
3 <ModuleList v-slot="slotProps">
4 <div class="card">
5 <h3>{{ slotProps.item.name }}</h3>
6 <span>{{ slotProps.item.status }}</span>
7 </div>
8 </ModuleList>
9
10 <!-- Avec déstructuration -->
11 <ModuleList v-slot="{ item, index }">
12 <div class="card">
13 <h3>#{{ index + 1 }}: {{ item.name }}</h3>
14 <span :class="item.status">{{ item.status }}</span>
15 </div>
16 </ModuleList>
17</template>Les deux écritures sont correctes et donnent les mêmes données ; la déstructuration t'évite simplement de répéter slotProps.. On ajoute 1 à index, car la numérotation de la boucle commence à zéro. Les données n'ont pas changé de propriétaire : la liste appartient toujours à ModuleList, et le parent n'a qu'un aperçu de chaque élément le temps du rendu.
Un slot default explicite
Placer v-slot sur la balise du composant est un raccourci pour le cas où tu n'utilises que le slot par défaut. Tu peux décrire le même slot explicitement avec une balise <template> et #default, et écrire la déstructuration dans sa valeur :
1<template>
2 <ModuleList>
3 <template #default="{ item }">
4 <p>{{ item.name }}</p>
5 </template>
6 </ModuleList>
7</template>L'effet est identique au précédent. La version avec <template> devient toutefois obligatoire quand tu remplis des slots nommés à côté du slot par défaut - Vue n'autorise alors pas v-slot sur le composant lui-même et signale une erreur de compilation, pour qu'il n'y ait aucun doute sur le slot auquel appartiennent les données.
Scoped slots nommés
Les scoped slots fonctionnent aussi avec les slots nommés. Le DataTable ci-dessous possède deux ports : header pour l'en-tête du tableau et row, qui reçoit item à chaque ligne. On suppose que le composant garde la liste items dans son script, comme ModuleList plus haut. Le parent reçoit les données avec #row="{ item }" :
1<!-- DataTable.vue -->
2<template>
3 <table>
4 <thead>
5 <tr>
6 <slot name="header">
7 <th>Name</th>
8 <th>Status</th>
9 </slot>
10 </tr>
11 </thead>
12 <tbody>
13 <tr v-for="item in items" :key="item.id">
14 <slot name="row" :item="item">
15 <td>{{ item.name }}</td>
16 <td>{{ item.status }}</td>
17 </slot>
18 </tr>
19 </tbody>
20 </table>
21</template>
22
23<!-- Parent -->
24<DataTable>
25 <template #header>
26 <th>Module Name</th>
27 <th>Current Status</th>
28 <th>Actions</th>
29 </template>
30
31 <template #row="{ item }">
32 <td>{{ item.name }}</td>
33 <td>{{ item.status }}</td>
34 <td><button>Inspect</button></td>
35 </template>
36</DataTable>Le parent a ajouté une troisième colonne avec un bouton "Inspect", et DataTable n'a pas changé d'une seule ligne. Remarque que l'attribut name n'est pas inclus dans les props du slot - Vue le réserve, donc item est la seule valeur que reçoit le parent.
Mon conseil : nomme les props du slot comme tu nommerais les props d'un composant - item, index, isSelected - et déstructure-les directement dans v-slot. Le code du parent se lit alors comme un template ordinaire. Les scoped slots reviendront dans la leçon sur les patterns avancés, où nous construirons un composant qui ne rend aucun HTML propre. Dans l'éditeur ci-dessous, ModuleList apparaît deux fois : avec le fallback par défaut et avec des cartes que le parent construit à partir de item et index.
Retiens : un scoped slot est un port avec un câble de données - l'enfant y fait passer des informations, et le parent décide comment les montrer.
Code de cette leçon : App.vue
1<script setup>
2import ModuleList from './ModuleList.vue'
3</script>
4
5<template>
6 <div class="nova-lab">
7 <h1>Démo des scoped slots</h1>
8
9 <h2>Rendu par défaut (fallback) :</h2>
10 <ModuleList />
11
12 <h2>Rendu personnalisé en cartes :</h2>
13 <ModuleList v-slot="{ item, index }">
14 <div class="custom-card">
15 <span class="index">#{{ index + 1 }}</span>
16 <strong>{{ item.name }}</strong>
17 <span
18 class="status"
19 :class="item.status"
20 >
21 {{ item.status }}
22 </span>
23 <span class="weight">{{ item.weight }} kg</span>
24 </div>
25 </ModuleList>
26 </div>
27</template>
28
29<style scoped>
30.nova-lab {
31 background: #0a0e27;
32 color: #00ff88;
33 min-height: 100vh;
34 padding: 20px;
35 font-family: monospace;
36}
37h1 { color: #00ff88; margin-bottom: 20px; }
38h2 { color: #00b4d8; margin: 20px 0 10px; font-size: 14px; }
39.custom-card {
40 display: flex;
41 align-items: center;
42 gap: 12px;
43 padding: 10px 15px;
44 border: 1px solid #00b4d8;
45 margin: 5px 0;
46 background: rgba(0, 180, 216, 0.05);
47}
48.index {
49 color: #666;
50 font-size: 12px;
51}
52.status {
53 padding: 2px 8px;
54 font-size: 11px;
55 border-radius: 3px;
56}
57.status.loaded { background: #00ff88; color: #0a0e27; }
58.status.pending { background: #ff9900; color: #0a0e27; }
59.status.damaged { background: #ff4444; color: #fff; }
60.weight { color: #666; margin-left: auto; }
61</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. À quoi servent les scoped slots dans Vue ?
2. Comment un composant enfant transmet-il des données à un scoped slot ?
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 ModuleList qui parcourt une liste et transmet chaque élément via un scoped slot. Le parent rend l'élément à sa manière.
- Clique dans l'ordre
Ordonne le code pour recevoir les données d'un scoped slot dans le parent :
- Éditeur de code
Crée un composant DataTable avec des scoped slots nommés : #header pour l'en-tête et #row="{ item }" pour la ligne du tableau.