Перейти к содержанию

Teleport

<Teleport> — это встроенный компонент, который позволяет нам «телепортировать» часть шаблона компонента в узел DOM, который существует вне иерархии DOM этого компонента.

Пример использования

Иногда часть шаблона компонента логически принадлежит ему, но с визуальной точки зрения она должна отображаться в другом месте DOM, возможно, даже за пределами приложения Vue.

Наиболее распространённым примером является создание полноэкранного модального окна. В идеале код кнопки для открытия модального окна и самого модального окна должен находиться в одном файле компонента, так как они связаны состоянием открытия/закрытия модального окна. Однако это приводит к тому, что модальное окно будет отображаться рядом с кнопкой, глубоко вложенной в иерархии DOM приложения. Это может вызвать сложные проблемы при позиционировании модального окна с помощью CSS.

Рассмотрим следующую структуру HTML:

template
<div class="outer">
  <h3>Пример Vue Teleport</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-index модального окна ограничивается содержащими его элементами. Если есть другой элемент, который перекрывает <div class="outer"> и имеет более высокий z-index, он будет закрывать наше модальное окно.

<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> через Devtools вашего браузера:

Можно комбинировать <Teleport> с <Transition> для создания анимированных модальных окон — смотрите Пример здесь.

Совет

Цель телепортации 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>A</div>
</Teleport>
<Teleport to="#modals">
  <div>B</div>
</Teleport>

Результат будет таким:

html
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

Отложенный телепорт

В Vue 3.5 и выше мы можем использовать параметр defer, чтобы отложить разрешение цели телепорта до тех пор, пока другие части приложения не смонтируются. Это позволяет телепорту нацелиться на элемент контейнера, который рендерится Vue, но находится в более поздней части дерева компонентов:

template
<Teleport defer to="#late-div">...</Teleport>
<!-- где-то позже в шаблоне -->
<div id="late-div"></div>

Обратите внимание, что целевой элемент должен быть отображен в том же монтировании / тике обновления, что и телепорт — т. е. если <div> будет установлен только через секунду, телепорт всё равно сообщит об ошибке. Отсрочка работает аналогично хуку жизненного цикла mounted.


Ссылки по теме

Teleport