Vue.jsMiddleTechnical

Что такое компонент 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-нюансы.

Sources

Related topics