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>
Deferred Teleport
In Vue 3.5 and above, we can use the defer
prop to defer the target resolving of a Teleport until other parts of the application have mounted. This allows the Teleport to target a container element that is rendered by Vue, but in a later part of the component tree:
template
<Teleport defer to="#late-div">...</Teleport>
<!-- somewhere later in the template -->
<div id="late-div"></div>
Note that the target element must be rendered in the same mount / update tick with the Teleport - i.e. if the <div>
is only mounted a second later, the Teleport will still report an error. The defer works similarly to the mounted
lifecycle hook.
Связанные