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>
Компонент содержит <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
.
Ссылки по теме