# Телепорты
Vue поощряет создавать пользовательские интерфейсы, инкапсулируя их и связанное с ним поведение в компоненты. Вкладывая компоненты друг в друга можно получить дерево компонентов, из которого и будет строиться пользовательский интерфейс приложения.
Но иногда случается, что часть этого шаблона логически принадлежит компоненту, хотя с технической точки зрения было бы удобнее переместить эту часть шаблона в какое-нибудь другое место в DOM или даже вне приложения Vue.
Один из частых сценариев — создание компонента, содержащего в себе полноэкранное модальное окно. В большинстве случаев, удобней когда логика модального окна внутри компонента, но позиционирование модального окна с помощью CSS становится сложной задачей, что может потребовать даже кардинальных изменений в композиции компонентов.
Рассмотрим следующую структуру HTML:
<body>
<div id="app" style="position: relative;">
<h3>Подсказки с помощью телепортов Vue 3</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>
2
3
4
5
6
7
8
Давайте взглянем на компонент modal-button
.
В нём есть button
для открытия модального окна и элемент div
с классом .modal
, где будет располагаться содержимое модального окна, а также кнопка для его закрытия.
const app = Vue.createApp({})
app.component('modal-button', {
template: `
<button type="button" @click="modalOpen = true">
Открыть полноэкранное модальное окно!
</button>
<div v-if="modalOpen" class="modal">
<div>
Информация в модальном окне!
<button type="button" @click="modalOpen = false">
Закрыть
</button>
</div>
</div>
`,
data() {
return {
modalOpen: false
}
}
})
app.mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
При использовании этого компонента внутри исходной структуры HTML становится видна проблема — модальное окно отрисовывается внутри глубоко вложенного div
, а его стиль position: absolute
получает родителя относительно расположенного div
по ссылке.
Телепорт же предоставляет прозрачный способ для управления, в каком месте DOM нужно отрисовать часть HTML, не требуя использовать глобальное состояние или разделять на два компонента.
Доработаем компонент modal-button
, чтобы использовать <teleport>
и скажем Vue «телепортировать часть его HTML в тег <body>».
app.component('modal-button', {
template: `
<button type="button" @click="modalOpen = true">
Открыть полноэкранное модальное окно! (с помощью телепорта!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
Я телепортированное модальное окно!
(Мой родитель "body")
<button type="button" @click="modalOpen = false">
Закрыть
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Теперь, при нажатии кнопки открытия модального окна, Vue корректно отобразит его содержимое в качестве дочернего тега body
.
See the Pen Телепорты Vue 3 by Vue (@Vue) on CodePen.
# Использование с компонентами
Если в <teleport>
расположен компонент Vue, то он всё равно логически остаётся дочерним компонентом родителя, в котором <teleport>
использован:
const app = Vue.createApp({
template: `
<h1>Корневой экземпляр</h1>
<parent-component />
`
})
app.component('parent-component', {
template: `
<h2>Это родительский компонент</h2>
<teleport to="#endofbody">
<child-component name="John" />
</teleport>
`
})
app.component('child-component', {
props: ['name'],
template: `
<div>Привет, {{ name }}</div>
`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
В этом случае, child-component
остаётся потомком компонента parent-component
и получит входной параметр name
от него, даже когда будет отрисован в другом месте.
Это также означает, что инъекции из родительского компонента будут работать как и ожидается, и что дочерний компонент будет вложен в родительский компонент во Vue Devtools, вместо того, чтобы отображаться там, куда он телепортировался фактически.
# Использование нескольких телепортов на одной цели
Подобный сценарий может произойти при переиспользовании компонента <Modal>
, когда одновременно может быть несколько его активных экземпляров. В таких случаях несколько компонентов <teleport>
будут монтировать своё содержимое к одному и тому же элементу. Порядок будет определяться временем добавления — в целевом элементе более поздние будут располагаться после тех монтирований, что произошли раньше.
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- результат -->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
2
3
4
5
6
7
8
9
10
11
12
Подробнее опции компонента <teleport>
можно прочитать в справочнике API.