# Телепорты
Vue предлагает создавать свои пользовательские интерфейсы, инкапсулируя UI и соответствующее поведение в компоненты. Вкладывая их друг в друга можно построить дерево компонентов, из которого и строится пользовательский интерфейс приложения.
Однако, иногда часть шаблона компонента принадлежит этому компоненту логически, в то время как с технической точки зрения было бы предпочтительнее переместить эту часть шаблона куда-то в другое место DOM или даже вне приложения Vue.
Частый случай такого — компонент, содержащий в себе полноэкранное модальное окно. В большинстве случаев, удобнее чтобы логика модального окна была внутри компонента, но тогда позиционирование модального окна становится сложной задачей для решения с помощью CSS, или требует кардинальных изменений в композиции компонентов.
Рассмотрим следующую структуру HTML:
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</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
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
При использовании этого компонента внутри исходной структуры 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 Teleport 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.