Kurs Vue.js · Moduł 8: Sloty i komponenty dynamiczne

Teleport

4 min czytania
W tej lekcji6

W NOVA LAB system alarmowy wyświetla alert na głównym ekranie stacji, niezależnie od tego, który moduł go wywołał. Vue ma <Teleport> - mechanizm renderowania treści w innym miejscu DOM, poza hierarchią komponentu.

Problem

Modale, toasty i popupy logicznie należą do komponentu, ale wizualnie powinny być renderowane na najwyższym poziomie DOM (np. bezpośrednio w <body>), aby uniknąć problemów z z-index, overflow: hidden i pozycjonowaniem. Wyobraź sobie alarm tlenowy zagnieżdżony w panelu modułu, który ma overflow: hidden: modal zostanie przycięty do rozmiaru panelu. Jeśli któryś przodek ma właściwość transform, nawet position: fixed przestaje liczyć się względem okna przeglądarki. Przenoszenie modala do komponentu App.vue rozwiązałoby problem z CSS, ale rozerwałoby logikę: stan alarmu żyje przecież w module tlenu.

Podstawy Teleport

Teleport pozwala zostawić kod modala tam, gdzie jest jego logika, i jednocześnie wyrenderować jego HTML w innym miejscu. W poniższym panelu przycisk ustawia showModal, a cały modal jest opakowany w <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>

Treść wewnątrz <Teleport> jest renderowana fizycznie w <body>, ale logicznie należy do komponentu - ma dostęp do jego danych i props. Zmienił się tylko adres w DOM: przekazywanie props, emitowanie zdarzeń i inject działają dokładnie tak jak wcześniej, a Vue Devtools nadal pokazują modal jako dziecko panelu.

Atrybut to

Atrybut to przyjmuje selektor CSS, a w razie potrzeby także gotowy element DOM. Najczęściej spotkasz trzy warianty:

1<!-- Do body -->
2<Teleport to="body">...</Teleport>
3
4<!-- Do elementu z ID -->
5<Teleport to="#modal-container">...</Teleport>
6
7<!-- Do elementu z klasą -->
8<Teleport to=".notifications">...</Teleport>

Cel musi już istnieć w DOM w chwili montowania Teleportu. Najlepiej, gdy jest to element poza całą aplikacją Vue, na przykład <div id="modal-container"> dodany w index.html obok #app. Jeśli wskażesz selektor, którego nie ma, Vue wypisze ostrzeżenie, a treść się nie pojawi.

Deferred Teleport

Od Vue 3.5 Teleport ma atrybut defer. Dzięki niemu cel może być wyrenderowany przez tę samą aplikację nieco dalej w szablonie, bo Vue szuka go dopiero po zamontowaniu reszty drzewa:

1<template>
2  <Teleport defer to="#station-log">
3    <p>Nowy wpis w dzienniku stacji</p>
4  </Teleport>
5
6  <!-- ten element renderuje się dopiero niżej w szablonie -->
7  <div id="station-log"></div>
8</template>

Bez defer ten sam kod szukałby #station-log, zanim element powstanie. Z defer cel musi się tylko pojawić w tym samym cyklu renderowania co Teleport.

Disabled Teleport

Możesz dynamicznie wyłączyć teleportację atrybutem disabled. Tutaj zmienna isMobile sprawia, że na telefonie popup renderuje się w miejscu, a na komputerze w <body>:

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

Gdy disabled ma wartość true, treść renderuje się w oryginalnym miejscu w drzewie komponentów. Zmiana flagi w trakcie działania przenosi ten sam element tam i z powrotem, bez tworzenia go od nowa.

Wiele Teleportów do jednego celu

Możesz mieć kilka <Teleport> renderujących do tego samego elementu - treść jest dołączana w kolejności montowania, więc późniejsze trafiają za wcześniejsze:

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>

W kontenerze #notifications pojawi się najpierw "Alert 1", a pod nim "Alert 2". Żaden Teleport nie nadpisuje treści pozostałych, dlatego ten wzorzec świetnie nadaje się na stos powiadomień.

Moja rada: dodaj w index.html osobny kontener na modale obok #app i teleportuj tam wszystkie okna alarmowe. body też zadziała, ale własny kontener porządkuje warstwy strony. W projekcie na końcu modułu Teleport wyświetli alarm krytyczny wywołany z listy cargo. W edytorze poniżej moduł tlenu wywołuje taki alarm: modal ląduje w <body>, choć jego stan żyje w App.vue.

Zapamiętaj: Teleport przenosi ekran alarmowy tam, gdzie widać go najlepiej, ale sterowanie zostaje w module, który go wywołał.

Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const showModal = ref(false)
5const alertMessage = ref('Krytyczny poziom tlenu: 15%')
6</script>
7
8<template>
9  <div class="nova-lab">
10    <h1>Demo: Teleport</h1>
11
12    <div class="module-panel">
13      <h2>Moduł tlenu</h2>
14      <p>Poziom: 15% - KRYTYCZNY</p>
15      <button @click="showModal = true" class="btn alert">
16        Pokaż alarm krytyczny
17      </button>
18    </div>
19
20    <div class="module-panel">
21      <h2>Moduł wody</h2>
22      <p>Poziom: 82% - w normie</p>
23    </div>
24
25    <!-- Modal przeniesiony przez Teleport do body -->
26    <Teleport to="body">
27      <div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
28        <div class="modal">
29          <h3>ALARM KRYTYCZNY</h3>
30          <p>{{ alertMessage }}</p>
31          <p>Wymagana natychmiastowa reakcja!</p>
32          <button @click="showModal = false" class="btn">
33            Zamknij alarm
34          </button>
35        </div>
36      </div>
37    </Teleport>
38  </div>
39</template>
40
41<style scoped>
42.nova-lab {
43  background: #0a0e27;
44  color: #00ff88;
45  min-height: 100vh;
46  padding: 20px;
47  font-family: monospace;
48}
49h1 { color: #00ff88; margin-bottom: 20px; }
50.module-panel {
51  border: 2px solid #00b4d8;
52  padding: 15px;
53  margin: 10px 0;
54  background: rgba(0, 180, 216, 0.05);
55}
56.module-panel h2 { color: #00b4d8; margin-bottom: 8px; }
57.btn {
58  background: #00b4d8;
59  color: #0a0e27;
60  border: none;
61  padding: 10px 20px;
62  cursor: pointer;
63  font-family: monospace;
64  font-weight: bold;
65}
66.btn.alert {
67  background: #ff4444;
68  color: white;
69}
70</style>
71
72<style>
73/* Style globalne dla modala przeniesionego przez Teleport */
74.modal-overlay {
75  position: fixed;
76  inset: 0;
77  background: rgba(0, 0, 0, 0.8);
78  display: flex;
79  align-items: center;
80  justify-content: center;
81  z-index: 9999;
82}
83.modal {
84  background: #1a1a2e;
85  border: 3px solid #ff4444;
86  padding: 30px;
87  max-width: 400px;
88  text-align: center;
89  color: #00ff88;
90  font-family: monospace;
91}
92.modal h3 {
93  color: #ff4444;
94  margin-bottom: 15px;
95  font-size: 20px;
96  letter-spacing: 2px;
97}
98.modal p { margin: 8px 0; }
99.modal button {
100  margin-top: 15px;
101  background: #ff4444;
102  color: white;
103  border: none;
104  padding: 10px 25px;
105  cursor: pointer;
106  font-family: monospace;
107  font-weight: bold;
108}
109</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co robi komponent <KeepAlive> opakowujący dynamiczny komponent?

  2. 2. Do czego służy <Teleport> w Vue?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Opakuj <component :is> w <KeepAlive> i sprawdź, że stan formularza jest zachowany po przełączeniu tabów.

  • Układanie w poziomie

    Ułóż składnię KeepAlive z dynamicznym komponentem:

  • Układanie w pionie

    Ułóż zdarzenia przy przełączaniu komponentu w <KeepAlive>:

  • Klikanie w kolejności

    Ułóż kroki implementacji modala z Teleport:

  • Edytor kodu

    Stwórz komponent AlertModal używający <Teleport to="body"> do renderowania modala poza hierarchią komponentu.

Przydatne artykuły