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>
с помощью инструментов разработчика вашего браузера:
Вы можете комбинировать <Teleport>
с <Transition>
чтобы создавать анимированные модальные окна — Вот пример.
Совет
Целевой элемент куда
мы телепортируем должен уже существовать в DOM, когда компонент <Teleport>
смонтирован. В идеале это должен быть элемент вне всего Vue приложения. Если вы нацеливаетесь на другой элемент, отрисованный Vue, вам нужно убедиться, что этот элемент будет смонтирован до компонента <Teleport>
.
Использование с компонентами
<Teleport>
изменяет только отображаемую структуру DOM, не затрагивая логическую иерархию компонентов. Другими словами, если <Teleport>
содержит компонент, этот компонент остается логическим дочерним элементом родительского компонента, содержащего <Teleport>
. Передача свойств и генерация событий продолжают работать так же, как и раньше.
Это также означает, что инъекции из родительского компонента работают ожидаемым образом, и дочерний компонент будет вложен под родительским компонентом в инструментах разработчика Vue, вместо того, чтобы быть размещенным там, куда перемещается фактическое содержимое.
Отключение телепорта
В некоторых случаях мы можем захотеть условно отключить <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>
Отложенный телепорт
В Vue 3.5 и выше можно использовать свойство defer
, чтобы отложить определение цели для телепорта до тех пор, пока другие части приложения не будут смонтированы. Это позволяет телепорту нацеливаться на контейнерный элемент, который отрисовывается Vue, но находится в более поздней части дерева компонентов:
template
<Teleport defer to="#late-div">...</Teleport>
<!-- где-то позже в шаблоне -->
<div id="late-div"></div>
Обратите внимание, что целевой элемент должен быть отрисован в рамках того же такта монтирования/обновления, что и телепорт. Если <div>
появится позже (например, через секунду), телепорт всё равно вызовет ошибку. Работа defer
аналогична хуку жизненного цикла mounted
.
Связанные