Что такое компонент Teleport в Vue 3?
<Teleport> рендерит содержимое компонента в другом месте DOM (например, в <body>) при сохранении реактивного контекста текущего компонента. Используется для модалей, тостов, тултипов — элементов, которые должны выйти за пределы родительского z-index или overflow.
Что такое Teleport
<Teleport> — встроенный компонент Vue 3, который «телепортирует» свой контент в любой DOM-узел за пределами текущего дерева компонентов. При этом реактивность, пропсы и эмиты работают как обычно — компонент логически остаётся в своём месте в дереве компонентов.
Синтаксис
<Teleport to="body">
<div class="modal-overlay">
<div class="modal">
<slot />
</div>
</div>
</Teleport>
Атрибут to принимает CSS-селектор или ссылку на DOM-элемент. Vue монтирует содержимое внутрь найденного элемента.
Практический пример: модальное окно
<!-- Modal.vue -->
<script setup>
const props = defineProps<{ modelValue: boolean; title: string }>()
const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void }>()
</script>
<template>
<Teleport to="body">
<Transition name="modal">
<div
v-if="modelValue"
class="fixed inset-0 bg-black/50 flex items-center justify-center"
@click.self="emit('update:modelValue', false)"
>
<div class="bg-white rounded-lg p-6 max-w-md w-full">
<h2>{{ title }}</h2>
<slot />
<button @click="emit('update:modelValue', false)">Закрыть</button>
</div>
</div>
</Transition>
</Teleport>
</template>
Использование в родителе:
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const show = ref(false)
</script>
<template>
<button @click="show = true">Открыть</button>
<Modal v-model="show" title="Подтверждение">
<p>Вы уверены?</p>
</Modal>
</template>
Атрибут disabled
Телепортацию можно временно отключить, сохраняя компонент на месте:
<Teleport to="body" :disabled="isMobile">
<Tooltip />
</Teleport>
Несколько Teleport в один контейнер
Vue поддерживает несколько <Teleport>, нацеленных на один DOM-узел. Они добавляются последовательно:
<!-- Первый toast -->
<Teleport to="#notifications"><Toast msg="Сохранено" /></Teleport>
<!-- Второй toast -->
<Teleport to="#notifications"><Toast msg="Ошибка" /></Teleport>
Подводные камни
- Целевой DOM-элемент (
to) должен существовать в момент монтирования компонента. Если он создаётся динамически, телепортация тихо провалится или вызовет предупреждение. - Стили, применяемые через
scoped-атрибут, не применяются к телепортированному контенту — он физически вне корневого элемента компонента. Используйте глобальные или:deep()стили. - SSR с Nuxt 3: серверный рендеринг не поддерживает телепортацию в произвольные узлы — только в
bodyчерез специальный<ClientOnly>или встроенный механизм Nuxt. - Вложенные
<Teleport>рендерятся отдельно — порядок z-index управляется CSS, а не деревом Vue. - При отладке в DevTools компонент отображается в дереве Vue на своём логическом месте, но в DOM — в целевом узле. Это может сбивать с толку при первичной отладке.
- Анимации (
<Transition>) внутри<Teleport>работают, но transition-group требует дополнительной настройки из-за иного DOM-контекста.
Common mistakes
- Размещать целевой контейнер в
<head>или вне корневого DOM. - Полагать, что
<Teleport>решает доступность сам — нет, фокус и role нужно прописать руками. - Использовать
<Teleport>для контента, который должен влиять на layout родителя — он его покидает. - Игнорировать SSR-нюансы и видеть гидрационные предупреждения.
What the interviewer is testing
- Знает синтаксис
<Teleport to>/disabled/defer. - Понимает, что логически компонент остаётся у родителя.
- Видит проблемы accessibility/focus-trap.
- Может объяснить SSR-нюансы.