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

Teleport

5 min de lecture
Dans cette leçon6

Au NOVA LAB, le système d'alarme affiche une alerte sur l'écran principal de la station, quel que soit le module qui l'a déclenchée. Vue propose <Teleport> - un mécanisme qui rend du contenu à un autre endroit du DOM, en dehors de la hiérarchie du composant.

Le problème

Les modales, les toasts et les popups appartiennent logiquement à un composant, mais visuellement ils devraient être rendus au plus haut niveau du DOM (par exemple directement dans <body>) pour éviter les problèmes de z-index, d'overflow: hidden et de positionnement. Imagine une alarme d'oxygène imbriquée dans un panneau de module qui a overflow: hidden : la modale est rognée à la taille du panneau. Si un ancêtre possède une propriété transform, même position: fixed cesse de se calculer par rapport à la fenêtre du navigateur. Déplacer la modale dans App.vue réglerait le problème CSS, mais déchirerait la logique : l'état de l'alarme vit après tout dans le module d'oxygène.

Les bases de Teleport

Teleport te permet de laisser le code de la modale là où se trouve sa logique, tout en rendant son HTML ailleurs. Dans le panneau ci-dessous, le bouton définit showModal, et toute la modale est enveloppée dans <Teleport to="body"> :

1<script setup>
2import { ref } from 'vue'
3const showModal = ref(false)
4</script>
5
6<template>
7  <div class="module-panel">
8    <h2>Oxygen Module</h2>
9    <button @click="showModal = true">Show Alert</button>
10
11    <Teleport to="body">
12      <div v-if="showModal" class="modal-overlay">
13        <div class="modal">
14          <h3>Critical Alert!</h3>
15          <p>Oxygen level below 20%</p>
16          <button @click="showModal = false">Dismiss</button>
17        </div>
18      </div>
19    </Teleport>
20  </div>
21</template>

Le contenu à l'intérieur de <Teleport> est physiquement rendu dans <body>, mais logiquement il appartient au composant - il a accès à ses données et à ses props. Seule l'adresse dans le DOM a changé : la transmission des props, l'émission d'événements et inject fonctionnent exactement comme avant, et les Vue Devtools montrent toujours la modale comme un enfant du panneau.

L'attribut to

L'attribut to accepte un sélecteur CSS et, si besoin, un véritable élément du DOM. Tu rencontreras le plus souvent trois variantes :

1<!-- Vers body -->
2<Teleport to="body">...</Teleport>
3
4<!-- Vers un élément avec un ID -->
5<Teleport to="#modal-container">...</Teleport>
6
7<!-- Vers un élément avec une classe -->
8<Teleport to=".notifications">...</Teleport>

La cible doit déjà exister dans le DOM au moment où le Teleport est monté. Idéalement, c'est un élément situé en dehors de toute l'application Vue, par exemple un <div id="modal-container"> ajouté dans index.html à côté de #app. Si tu indiques un sélecteur qui n'existe pas, Vue affiche un avertissement et le contenu n'apparaît pas.

Teleport différé

Depuis Vue 3.5, Teleport possède un attribut defer. Grâce à lui, la cible peut être rendue par la même application un peu plus bas dans le template, car Vue ne la cherche qu'après le montage du reste de l'arbre :

1<template>
2  <Teleport defer to="#station-log">
3    <p>Nouvelle entrée dans le journal de la station</p>
4  </Teleport>
5
6  <!-- cet élément n'est rendu que plus bas dans le template -->
7  <div id="station-log"></div>
8</template>

Sans defer, le même code chercherait #station-log avant que l'élément n'existe. Avec defer, la cible doit seulement apparaître dans le même cycle de rendu que le Teleport.

Désactiver Teleport

Tu peux désactiver la téléportation de façon dynamique avec l'attribut disabled. Ici, la variable isMobile fait que le popup est rendu sur place sur un téléphone et dans <body> sur un ordinateur :

1<script setup>
2import { ref } from 'vue'
3const isMobile = ref(false)
4</script>
5
6<template>
7  <!-- Sur mobile, rendu local ; sur desktop, dans body -->
8  <Teleport to="body" :disabled="isMobile">
9    <div class="popup">
10      <p>Popup content</p>
11    </div>
12  </Teleport>
13</template>

Quand disabled vaut true, le contenu est rendu à son emplacement d'origine dans l'arbre des composants. Changer l'indicateur pendant l'exécution déplace le même élément d'un endroit à l'autre, sans le recréer.

Plusieurs Teleports vers une même cible

Tu peux avoir plusieurs <Teleport> qui rendent dans le même élément - le contenu est ajouté dans l'ordre de montage, donc les plus récents se placent après les précédents :

1<template>
2  <Teleport to="#notifications">
3    <div class="toast">Alert 1</div>
4  </Teleport>
5
6  <Teleport to="#notifications">
7    <div class="toast">Alert 2</div>
8  </Teleport>
9</template>

Le conteneur #notifications affiche d'abord "Alert 1", puis "Alert 2" en dessous. Aucun Teleport n'écrase le contenu des autres, c'est pourquoi ce pattern convient parfaitement à une pile de notifications.

Mon conseil : ajoute dans index.html un conteneur dédié aux modales à côté de #app, et téléporte-y toutes les fenêtres d'alarme. body fonctionne aussi, mais un conteneur dédié garde les couches de la page bien rangées. Dans le projet de fin de module, Teleport affichera une alarme critique déclenchée depuis la liste du fret. Dans l'éditeur ci-dessous, le module d'oxygène déclenche une telle alarme : la modale atterrit dans <body>, alors que son état vit dans App.vue.

Retiens : Teleport déplace l'écran d'alarme là où on le voit le mieux, mais les commandes restent dans le module qui l'a déclenchée.

Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3
4const showModal = ref(false)
5const alertMessage = ref('Niveau d\'oxygène critique : 15%')
6</script>
7
8<template>
9  <div class="nova-lab">
10    <h1>Démo Teleport</h1>
11
12    <div class="module-panel">
13      <h2>Module d'oxygène</h2>
14      <p>Niveau : 15% - CRITIQUE</p>
15      <button @click="showModal = true" class="btn alert">
16        Afficher l'alerte critique
17      </button>
18    </div>
19
20    <div class="module-panel">
21      <h2>Module d'eau</h2>
22      <p>Niveau : 82% - Normal</p>
23    </div>
24
25    <!-- Modale téléportée dans body -->
26    <Teleport to="body">
27      <div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
28        <div class="modal">
29          <h3>ALERTE CRITIQUE</h3>
30          <p>{{ alertMessage }}</p>
31          <button @click="showModal = false" class="btn">
32            Ignorer l'alerte
33          </button>
34        </div>
35      </div>
36    </Teleport>
37  </div>
38</template>
39
40<style scoped>
41.nova-lab {
42  background: #0a0e27;
43  color: #00ff88;
44  min-height: 100vh;
45  padding: 20px;
46  font-family: monospace;
47}
48h1 { color: #00ff88; margin-bottom: 20px; }
49.module-panel {
50  border: 2px solid #00b4d8;
51  padding: 15px;
52  margin: 10px 0;
53  background: rgba(0, 180, 216, 0.05);
54}
55.module-panel h2 { color: #00b4d8; margin-bottom: 8px; }
56.btn {
57  background: #00b4d8;
58  color: #0a0e27;
59  border: none;
60  padding: 10px 20px;
61  cursor: pointer;
62  font-family: monospace;
63  font-weight: bold;
64}
65.btn.alert {
66  background: #ff4444;
67  color: white;
68}
69</style>
70
71<style>
72/* Styles globaux pour la modale téléportée */
73.modal-overlay {
74  position: fixed;
75  inset: 0;
76  background: rgba(0, 0, 0, 0.8);
77  display: flex;
78  align-items: center;
79  justify-content: center;
80  z-index: 9999;
81}
82.modal {
83  background: #1a1a2e;
84  border: 3px solid #ff4444;
85  padding: 30px;
86  max-width: 400px;
87  text-align: center;
88  color: #00ff88;
89  font-family: monospace;
90}
91.modal h3 {
92  color: #ff4444;
93  margin-bottom: 15px;
94  font-size: 20px;
95  letter-spacing: 2px;
96}
97.modal p { margin: 8px 0; }
98.modal button {
99  margin-top: 15px;
100  background: #ff4444;
101  color: white;
102  border: none;
103  padding: 10px 25px;
104  cursor: pointer;
105  font-family: monospace;
106  font-weight: bold;
107}
108</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. Que fait le composant <KeepAlive> lorsqu'il enveloppe un composant dynamique ?

  2. 2. À quoi sert <Teleport> dans Vue ?

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

    Enveloppe <component :is> dans <KeepAlive> et vérifie que l'état du formulaire est préservé après un changement d'onglet.

  • Tri horizontal

    Ordonne la syntaxe de KeepAlive avec un composant dynamique :

  • Tri vertical

    Ordonne les événements lors du changement d'un composant à l'intérieur de <KeepAlive> :

  • Clique dans l'ordre

    Ordonne les étapes de mise en place d'une modale avec Teleport :

  • Éditeur de code

    Crée un composant AlertModal en utilisant <Teleport to="body"> pour rendre la modale en dehors de la hiérarchie des composants.