Skip to content

Teleport

<Teleport> — це вбудований компонент, який дозволяє нам "телепортувати" частину шаблону компонента у вузол DOM, який існує поза ієрархією DOM цього компонента.

Основне використання

Іноді ми можемо зіткнутися з таким сценарієм: частина шаблону компонента логічно належить до нього, але з візуальної точки зору вона повинна відображатися в іншому місці DOM, поза програмою Vue.

Найпоширенішим прикладом цього є створення повноекранного режиму. В ідеалі ми хочемо, щоб кнопка модального вікна та саме вікно жили в одному компоненті, оскільки вони обидва пов’язані зі станом відкриття/закриття модального вікна. Але це означає, що модальне вікно відображатиметься поруч із кнопкою, глибоко вкладеною в ієрархію DOM програми. Це може створити деякі складнощі під час позиціонування вікна через CSS.

Розглянемо наступну HTML-структуру.

template
<div class="outer">
  <h3>Приклад телепортації</h3>
  <div>
    <MyModal />
  </div>
</div>

А ось реалізація <MyModal>:

vue
<script setup>
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
  <button @click="open = true">Відкрити модальне вікно</button>

  <div v-if="open" class="modal">
    <p>Привіт з модального вікна!</p>
    <button @click="open = false">Закрити</button>
  </div>
</template>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}
</style>
vue
<script>
export default {
  data() {
    return {
      open: false
    }
  }
}
</script>

<template>
  <button @click="open = true">Відкрити модальне вікно</button>

  <div v-if="open" class="modal">
    <p>Привіт з модального вікна!</p>
    <button @click="open = false">Закрити</button>
  </div>
</template>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}
</style>

Компонент містить <button> для ініціювання відкриття модального вікна та <div> з класом .modal, який має зміст та кнопку для самозакриття.

Під час використання цього компонента в початковій HTML-структурі існує низка потенційних проблем:

  • position: fixed лише розміщує елемент відносно вікна перегляду, якщо жоден елемент-предок не має властивостей transform, perspective або filter. Якщо, наприклад, ми маємо намір анімувати предка <div class="outer"> за допомогою перетворення CSS, це порушить макет!

  • z-індекс модального вікна обмежений елементами, що містяться в ньому. Якщо є інший елемент, який збігається з <div class="outer"> і має вищий z-індекс, він покриватиме наше вікно.

<Teleport> забезпечує простий спосіб обійти це, дозволяючи нам вийти за межі вкладеної структури DOM. Змінимо <MyModal> на використання <Teleport>:

template
<button @click="open = true">Відкрити модальне вікно</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Привіт з модального вікна!</p>
    <button @click="open = false">Закрити</button>
  </div>
</Teleport>

Ціль to для <Teleport> очікує рядок CSS селектору або фактичний вузол DOM. Тут ми, по суті, кажемо Vue «телепортувати цей фрагмент шаблону до тегу body».

Ви можете натиснути кнопку нижче та перевірити тег <body> за допомогою інструментів розробника вашого браузера:

Ви можете поєднати <Teleport> з <Transition>, щоб створити анімовані модальні вікна - див. Приклад тут.

TIP

Ціль телепорту to має бути вже в DOM, коли монтується компонент <Teleport>. В ідеалі це має бути елемент за межами всієї програми Vue. Якщо націлено на інший елемент, відтворений Vue, вам потрібно переконатися, що цей елемент змонтовано перед <Teleport>.

Використання з компонентами

<Teleport> змінює лише відтворену структуру DOM, але не впливає на логічну ієрархію компонентів. Тобто, якщо <Teleport> містить компонент, цей компонент залишатиметься логічним дочірнім компонентом батьківського компонента, що містить <Teleport>. Передача реквізитів і випромінювання подій працюватимуть так само.

Це також означає, що ін’єкції від батьківського компонента працюють належним чином і що дочірній компонент буде вкладено під батьківським компонентом у Vue Devtools, а не розміщуватиметься там, куди переміщено фактичний вміст.

Вимкнення телепорту

У деяких випадках ми можемо забажати вимкнути <Teleport>. Наприклад, ми можемо захотіти відобразити компонент як накладення на великих екранах, але вбудовано на мобільному пристрої. <Teleport> підтримує реквізит disabled, яку можна динамічно перемикати:

template
<Teleport :disabled="isMobile">
  ...
</Teleport>

Де стан isMobile можна динамічно оновлювати, виявляючи зміни медіа-запиту.

Кілька телепортів для однієї цілі

Загальним випадком використання буде повторно використовуваний компонент <Modal> з можливістю одночасної активності кількох екземплярів. Для такого сценарію кілька компонентів <Teleport> можуть монтувати свій вміст до одного цільового елемента. Порядок буде простим додаванням - пізніші кріплення будуть розташовані після попередніх у цільовому елементі.

Приклад використання:

template
<Teleport to="#modals">
  <div>А</div>
</Teleport>
<Teleport to="#modals">
  <div>Б</div>
</Teleport>

Отриманий результат буде таким:

html
<div id="modals">
  <div>А</div>
  <div>Б</div>
</div>

Пов'язані

Teleport has loaded