Vue 3 Teleport та Suspense: Просунуті Патерни та Питання на Співбесідах 2026
Повний посібник з Vue 3 Teleport та Suspense - просунуті патерни рендерингу, обробка помилок та найпоширеніші питання на співбесідах для сеньйорів Vue.

Vue 3 Teleport та Suspense вирішують дві різні проблеми рендерингу: розміщення компонентів за межами їхньої DOM-ієрархії та координація асинхронних залежностей. Обидва є вбудованими компонентами, що не потребують імпорту, і обидва регулярно з'являються на співбесідах для сеньйор Vue-розробників.
Vue 3.5 додав проп defer до Teleport, дозволяючи цілі, що рендеряться пізніше в дереві компонентів. Це вирішує поширену проблему SSR, коли ціль порталу не існує в момент монтування.
Основи Teleport та Проп defer
Teleport переміщує відрендерений контент до іншого місця в DOM, зберігаючи дерево компонентів Vue. Компонент залишається логічним нащадком свого батька: пропси, події та provide/inject працюють нормально. Vue DevTools показує телепортований компонент вкладеним під його батьком, а не під цільовим елементом.
<!-- ModalTrigger.vue -->
<script setup>
import { ref } from 'vue'
const isOpen = ref(false)
</script>
<template>
<button @click="isOpen = true">Open Modal</button>
<Teleport to="#modal-root">
<div v-if="isOpen" class="modal-overlay" @click.self="isOpen = false">
<div class="modal-content">
<slot />
<button @click="isOpen = false">Close</button>
</div>
</div>
</Teleport>
</template>Проп to приймає рядок CSS-селектора або посилання на DOM-елемент. Коли ціль не існує в момент монтування, Teleport виводить попередження і нічого не рендерить. Це створює проблеми з SSR та динамічними макетами.
Проп defer (Vue 3.5+)
Проп defer повідомляє Teleport почекати, поки інші частини застосунку змонтуються, перш ніж визначати ціль. Це дозволяє телепортувати до елементів, що рендеряться пізніше в тому ж компоненті або в сусідніх компонентах.
<!-- App.vue -->
<template>
<Teleport defer to="#dynamic-target">
<NotificationBanner />
</Teleport>
<!-- Target rendered after the Teleport -->
<div id="dynamic-target"></div>
</template>Без defer цей код не спрацює, оскільки #dynamic-target не існує, коли Teleport монтується. З defer Vue визначає ціль після того, як усі компоненти в тому ж тіку змонтовані. Ціль все одно повинна рендеритися в тому ж циклі монтування або оновлення.
Множинні Teleport та Умовний Рендеринг
Кілька компонентів Teleport можуть мати одну й ту ж ціль. Контент додається в порядку оголошення:
<template>
<Teleport to="#notifications">
<Toast message="First" />
</Teleport>
<Teleport to="#notifications">
<Toast message="Second" />
</Teleport>
</template>
<!-- Result in #notifications:
<div>First</div>
<div>Second</div>
-->Проп disabled контролює, чи відбувається телепортація. Коли вимкнено, контент рендериться inline. Цей патерн обробляє адаптивні макети, де модалі повинні накладатися на десктопі, але рендеритися inline на мобільних:
<script setup>
import { useMediaQuery } from '@vueuse/core'
const isMobile = useMediaQuery('(max-width: 768px)')
</script>
<template>
<Teleport to="body" :disabled="isMobile">
<MobileDrawer />
</Teleport>
</template>Suspense для Оркестрації Асинхронних Компонентів
Suspense координує асинхронні залежності по дереву компонентів. Коли будь-який компонент-нащадок має невирішену асинхронну залежність, Suspense відображає контент слота fallback. Коли всі залежності вирішені, Suspense перемикається на слот default.
Suspense залишається експериментальним у Vue 3.5. API може змінитися до досягнення стабільного статусу. Використання в продакшені вимагає прийняття цього ризику.
<!-- Dashboard.vue -->
<template>
<Suspense>
<template #default>
<DashboardContent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>Suspense відстежує два типи асинхронних залежностей: компоненти з async setup() та асинхронні компоненти, створені через defineAsyncComponent.
Async Setup з Top-Level Await
Vue 3 script setup підтримує top-level await. Будь-який компонент, що використовує top-level await, стає асинхронною залежністю, яку Suspense може відстежувати:
<!-- UserProfile.vue -->
<script setup>
const response = await fetch('/api/user/profile')
const user = await response.json()
</script>
<template>
<div class="profile">
<h1>{{ user.name }}</h1>
<p>{{ user.email }}</p>
</div>
</template>Цей компонент не може відрендеритися, поки fetch не завершиться. Батьківська межа Suspense відображає fallback до вирішення. Без межі Suspense компонент просто не рендериться.
Вкладений Suspense з Пропом suspensible
Складні застосунки часто мають кілька асинхронних меж. Vue 3.3 представив проп suspensible для контролю того, як вкладені компоненти Suspense взаємодіють зі своїми батьками.
<!-- PageLayout.vue -->
<template>
<Suspense>
<template #default>
<header>
<AsyncNavigation />
</header>
<main>
<!-- Inner Suspense with its own fallback -->
<Suspense suspensible>
<template #default>
<AsyncContent />
</template>
<template #fallback>
<ContentSkeleton />
</template>
</Suspense>
</main>
</template>
<template #fallback>
<PageSkeleton />
</template>
</Suspense>
</template>Коли встановлено suspensible, внутрішній Suspense реєструється в батьківській межі Suspense. Батько чекає вирішення і AsyncNavigation, і AsyncContent перед приховуванням свого fallback. Внутрішній Suspense все ще служить локальною межею: якщо AsyncContent займає більше часу, ContentSkeleton відображається, поки AsyncNavigation залишається видимою.
Без suspensible внутрішній Suspense працює незалежно. Батько показує свій fallback лише під час завантаження AsyncNavigation, повністю ігноруючи AsyncContent.
Готовий до співбесід з Vue.js / Nuxt.js?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Поєднання Teleport, Suspense та Transitions
Реальні застосунки поєднують ці патерни. Модал, що завантажує контент асинхронно, виграє від усіх трьох:
<!-- AsyncModal.vue -->
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const isOpen = ref(false)
const AsyncModalContent = defineAsyncComponent({
loader: () => import('./ModalContent.vue'),
loadingComponent: () => import('./ModalSkeleton.vue'),
delay: 200,
timeout: 10000
})
</script>
<template>
<button @click="isOpen = true">Open</button>
<Teleport to="body">
<Transition name="modal">
<div v-if="isOpen" class="modal-wrapper">
<Suspense>
<template #default>
<AsyncModalContent @close="isOpen = false" />
</template>
<template #fallback>
<ModalSkeleton />
</template>
</Suspense>
</div>
</Transition>
</Teleport>
</template>
<style>
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
</style>Порядок вкладення має значення: Teleport обгортає Transition, який обгортає умовний елемент, який містить Suspense. Це гарантує, що перехід анімує весь модал, включаючи стани завантаження.
Обробка Помилок з Suspense
Suspense не обробляє помилки. Невдалі асинхронні залежності відхиляють свої проміси та поширюють помилки вгору по дереву компонентів. Хук життєвого циклу onErrorCaptured перехоплює ці помилки на межі:
<!-- ErrorBoundary.vue -->
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err) => {
error.value = err
return false // Prevent propagation
})
</script>
<template>
<div v-if="error" class="error-state">
<p>Failed to load: {{ error.message }}</p>
<button @click="error = null">Retry</button>
</div>
<Suspense v-else>
<template #default>
<slot />
</template>
<template #fallback>
<slot name="loading" />
</template>
</Suspense>
</template>Цей патерн обгортає будь-яке дерево асинхронних компонентів станами і завантаження, і помилок. Кнопка повтору очищає помилку, змушуючи Vue перерендерити та повторити асинхронну операцію.
Питання на Співбесідах про Teleport та Suspense
Позиції сеньйорів Vue все частіше тестують ці патерни. Типові питання та що відрізняє сильні відповіді:
П: Коли слід використовувати Teleport замість CSS-позиціонування?
Слабка відповідь: "Коли z-index не працює."
Сильна відповідь: Teleport вирішує проблеми DOM-ієрархії, які CSS не може. Модал всередині контейнера з overflow: hidden або transform створює новий контекст накладання, ламаючи position: fixed. Teleport переміщує модал до body, уникаючи цих обмежень, зберігаючи логічне дерево компонентів Vue.
П: Чим Suspense відрізняється від станів завантаження в кожному компоненті?
Слабка відповідь: "Suspense чистіший."
Сильна відповідь: Suspense координує кілька асинхронних залежностей. Десять компонентів з індивідуальними станами завантаження блимають десятьма спінерами в різний час. Suspense показує один спінер, поки всі десять не вирішаться, потім показує їх разом. Це створює плавнішу сприйняту продуктивність і простіший код компонентів, оскільки асинхронні компоненти не повинні керувати власними станами завантаження.
П: Що відбувається, коли ціль Teleport не існує?
Контент не рендериться, і Vue логує попередження. Проп defer у Vue 3.5 вирішує це, чекаючи, поки інші компоненти спочатку змонтуються. Без defer потрібно переконатися, що цілі існують в index.html або в батьківському компоненті, який монтується перед нащадками.
П: Чи працює Suspense з Options API?
Так. Будь-який компонент з async setup() запускає Suspense, незалежно від того, чи використовує він Composition API чи Options API в інших місцях. Функція setup є асинхронною межею, а не стиль компонента.
Teleport та Suspense в Nuxt 3
Nuxt 3 розширює ці патерни з урахуванням SSR. Документація Nuxt про SSR охоплює тайминг гідрації.
Для Teleport Nuxt вимагає, щоб ціль існувала перед гідрацією. Додайте цілі порталів до app.vue або використовуйте проп defer:
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<!-- Portal targets for SSR -->
<div id="modal-root"></div>
<div id="toast-root"></div>
</template>Для Suspense composables useFetch та useAsyncData Nuxt інтегруються автоматично. Компоненти сторінок, що використовують ці composables, працюють із вбудованим Suspense Nuxt без ручного налаштування. Див. модуль Vue composables SharpSkill для пов'язаної підготовки до співбесід.
Продакшен-Патерни для Vue 3.5
- Цілі Teleport в index.html: Створюйте стабільні цілі порталів, що існують до запуску будь-якого коду Vue. Це повністю уникає проблем з таймінгом.
- Гранулярні межі Suspense: Один Suspense на незалежну одиницю завантаження. Дашборд з окремими віджетами виграє від окремих меж Suspense, тому швидкі віджети з'являються негайно.
- Межі помилок над Suspense: Завжди обгортайте Suspense обробкою помилок. Мережеві збої та помилки API не повинні валити все дерево.
- Тайминг переходів: При поєднанні Suspense з Transition встановлюйте
mode="out-in", щоб запобігти накладанню під час зміни станів.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Чи знайдеш ти помилку в Vue.js / Nuxt.js?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 28 серпня 2026 р.
Теги
Поділитися
Пов'язані статті

Vue 3 з TypeScript у 2026: типобезпечні props, emits та composables
Практичний посібник із типізації компонентів Vue 3 у TypeScript: узагальнені defineProps, кортежні defineEmits, типізовані composables, defineModel та InjectionKey разом із питаннями для співбесід.

Vue 3 Composition API: Повний посібник з реактивності та композиції
Практичний посібник з Vue 3 Composition API. ref, reactive, computed, watch і composables — усе необхідне для створення продуктивних Vue-застосунків.

Vue 3 Script Setup та defineModel у 2026: Сучасний Синтаксис та Питання на Співбесідах
Вичерпний посібник з синтаксису script setup у Vue 3 та макросу defineModel. Огляд reactive props destructure, типізації TypeScript та питань на співбесідах.