Vue 3 Teleport i Suspense: Zaawansowane Wzorce i Pytania Rekrutacyjne 2026

Kompleksowy przewodnik po Vue 3 Teleport i Suspense - zaawansowane wzorce renderowania, obsługa błędów oraz najczęstsze pytania na rozmowach kwalifikacyjnych dla seniorów Vue.

Vue 3 Teleport i Suspense zaawansowane wzorce

Vue 3 Teleport i Suspense rozwiązują dwa odrębne problemy renderowania: umieszczanie komponentów poza ich hierarchią DOM oraz koordynowanie asynchronicznych zależności. Oba są wbudowanymi komponentami niewymagającymi importów i oba pojawiają się regularnie na rozmowach kwalifikacyjnych dla seniorów Vue.

Aktualizacja Teleport w Vue 3.5

Vue 3.5 dodał prop defer do Teleport, umożliwiając cele renderowane później w drzewie komponentów. To rozwiązuje częsty problem SSR, gdy cel portalu nie istnieje w momencie montowania.

Podstawy Teleport i Prop defer

Teleport przenosi wyrenderowaną zawartość do innej lokalizacji DOM, zachowując jednocześnie drzewo komponentów Vue. Komponent pozostaje logicznym dzieckiem swojego rodzica: propsy, zdarzenia i provide/inject działają normalnie. Vue DevTools pokazuje przeteleportowany komponent zagnieżdżony pod jego rodzicem, nie pod elementem docelowym.

vue
<!-- 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>

Prop to przyjmuje string z selektorem CSS lub referencję do elementu DOM. Gdy cel nie istnieje w momencie montowania, Teleport wyświetla ostrzeżenie i nie renderuje niczego. To tworzy problemy z SSR i dynamicznymi layoutami.

Prop defer (Vue 3.5+)

Prop defer informuje Teleport, aby poczekał, aż inne części aplikacji zostaną zamontowane przed rozwiązaniem celu. To umożliwia teleportację do elementów renderowanych później w tym samym komponencie lub w komponentach-rodzeństwie.

vue
<!-- App.vue -->
<template>
  <Teleport defer to="#dynamic-target">
    <NotificationBanner />
  </Teleport>

  <!-- Target rendered after the Teleport -->
  <div id="dynamic-target"></div>
</template>

Bez defer ten kod zawiedzie, ponieważ #dynamic-target nie istnieje gdy Teleport się montuje. Z defer, Vue rozwiązuje cel po tym jak wszystkie komponenty w tym samym cyklu zostały zamontowane. Cel musi nadal renderować się w tym samym cyklu montowania lub aktualizacji.

Wiele Teleportów i Renderowanie Warunkowe

Wiele komponentów Teleport może celować w ten sam element. Zawartość dodawana jest w kolejności deklaracji:

vue
<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>
-->

Prop disabled kontroluje czy teleportacja ma miejsce. Gdy wyłączony, zawartość renderuje się inline. Ten wzorzec obsługuje responsywne layouty, gdzie modale powinny nakładać się na desktopie, ale renderować inline na urządzeniach mobilnych:

vue
<script setup>
import { useMediaQuery } from '@vueuse/core'

const isMobile = useMediaQuery('(max-width: 768px)')
</script>

<template>
  <Teleport to="body" :disabled="isMobile">
    <MobileDrawer />
  </Teleport>
</template>

Suspense dla Orkiestracji Komponentów Asynchronicznych

Suspense koordynuje asynchroniczne zależności w drzewie komponentów. Gdy jakikolwiek komponent potomny ma nierozwiązaną zależność asynchroniczną, Suspense wyświetla zawartość slotu fallback. Po rozwiązaniu wszystkich zależności, Suspense przełącza się na slot domyślny.

Status Eksperymentalny

Suspense pozostaje eksperymentalny w Vue 3.5. API może zmienić się przed osiągnięciem stabilnego statusu. Użycie produkcyjne wymaga akceptacji tego ryzyka.

vue
<!-- Dashboard.vue -->
<template>
  <Suspense>
    <template #default>
      <DashboardContent />
    </template>

    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

Suspense śledzi dwa typy zależności asynchronicznych: komponenty z async setup() oraz komponenty asynchroniczne utworzone przez defineAsyncComponent.

Async Setup z Top-Level Await

Vue 3 script setup wspiera top-level await. Każdy komponent używający top-level await staje się zależnością asynchroniczną, którą Suspense może śledzić:

vue
<!-- 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>

Ten komponent nie może się wyrenderować dopóki fetch nie zakończy się. Nadrzędna granica Suspense wyświetla fallback do momentu rozwiązania. Bez granicy Suspense, komponent po prostu się nie renderuje.

Zagnieżdżony Suspense z Propem suspensible

Złożone aplikacje często mają wiele granic asynchronicznych. Vue 3.3 wprowadził prop suspensible do kontrolowania interakcji zagnieżdżonych komponentów Suspense z ich rodzicami.

vue
<!-- 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>

Gdy suspensible jest ustawiony, wewnętrzny Suspense rejestruje się u nadrzędnej granicy Suspense. Rodzic czeka na rozwiązanie zarówno AsyncNavigation jak i AsyncContent przed ukryciem swojego fallbacku. Wewnętrzny Suspense nadal służy jako lokalna granica: jeśli AsyncContent trwa dłużej, ContentSkeleton wyświetla się podczas gdy AsyncNavigation pozostaje widoczna.

Bez suspensible, wewnętrzny Suspense działa niezależnie. Rodzic pokazuje swój fallback tylko podczas ładowania AsyncNavigation, całkowicie ignorując AsyncContent.

Gotowy na rozmowy o Vue.js / Nuxt.js?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Łączenie Teleport, Suspense i Transitions

Rzeczywiste aplikacje łączą te wzorce. Modal ładujący zawartość asynchronicznie korzysta ze wszystkich trzech:

vue
<!-- 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>

Kolejność zagnieżdżenia ma znaczenie: Teleport opakowuje Transition, który opakowuje element warunkowy, który zawiera Suspense. To zapewnia, że przejście animuje cały modal włącznie ze stanami ładowania.

Obsługa Błędów z Suspense

Suspense nie obsługuje błędów. Nieudane zależności asynchroniczne odrzucają swoje obietnice i propagują błędy w górę drzewa komponentów. Hook cyklu życia onErrorCaptured przechwytuje te błędy na granicy:

vue
<!-- 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>

Ten wzorzec opakowuje każde drzewo asynchronicznych komponentów zarówno stanami ładowania jak i błędów. Przycisk ponowienia czyści błąd, powodując ponowne renderowanie Vue i ponowną próbę operacji asynchronicznej.

Pytania Rekrutacyjne o Teleport i Suspense

Stanowiska seniorów Vue coraz częściej testują te wzorce. Typowe pytania i co wyróżnia mocne odpowiedzi:

P: Kiedy należy użyć Teleport zamiast pozycjonowania CSS?

Słaba odpowiedź: "Gdy z-index nie działa."

Mocna odpowiedź: Teleport rozwiązuje problemy hierarchii DOM, których CSS nie może. Modal wewnątrz kontenera z overflow: hidden lub transform tworzy nowy kontekst warstw, psując position: fixed. Teleport przenosi modal do body, uciekając od tych ograniczeń przy zachowaniu logicznego drzewa komponentów Vue.

P: Jak Suspense różni się od stanów ładowania w każdym komponencie?

Słaba odpowiedź: "Suspense jest czystszy."

Mocna odpowiedź: Suspense koordynuje wiele zależności asynchronicznych. Dziesięć komponentów z indywidualnymi stanami ładowania błyska dziesięcioma spinnerami w różnym czasie. Suspense pokazuje jeden spinner dopóki wszystkie dziesięć nie zostaną rozwiązane, potem ujawnia je razem. To tworzy płynniejszą postrzeganą wydajność i prostszy kod komponentów, ponieważ komponenty asynchroniczne nie muszą zarządzać własnymi stanami ładowania.

P: Co się dzieje gdy cel Teleport nie istnieje?

Zawartość nie renderuje się i Vue loguje ostrzeżenie. Prop defer w Vue 3.5 rozwiązuje to, czekając aż inne komponenty najpierw się zamontują. Bez defer, należy upewnić się że cele istnieją w index.html lub w komponencie nadrzędnym, który montuje się przed dziećmi.

P: Czy Suspense działa z Options API?

Tak. Każdy komponent z async setup() uruchamia Suspense, niezależnie od tego czy używa Composition API czy Options API gdzie indziej. Funkcja setup jest granicą asynchroniczną, nie styl komponentu.

Teleport i Suspense w Nuxt 3

Nuxt 3 rozszerza te wzorce o rozważania SSR. Dokumentacja Nuxt o SSR opisuje timing hydracji.

Dla Teleport, Nuxt wymaga aby cel istniał przed hydracją. Należy dodać cele portali do app.vue lub użyć prop defer:

vue
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>

  <!-- Portal targets for SSR -->
  <div id="modal-root"></div>
  <div id="toast-root"></div>
</template>

Dla Suspense, composable useFetch i useAsyncData Nuxta integrują się automatycznie. Komponenty stron używające tych composables działają z wbudowanym Suspense Nuxta bez ręcznej konfiguracji. Zobacz moduł Vue composables SharpSkill dla powiązanego przygotowania do rozmów.

Wzorce Produkcyjne dla Vue 3.5

  • Cele Teleport w index.html: Tworzenie stabilnych celów portali istniejących przed uruchomieniem jakiegokolwiek kodu Vue. To całkowicie unika problemów z timingiem.
  • Granularne granice Suspense: Jeden Suspense na niezależną jednostkę ładowania. Dashboard z oddzielnymi widgetami korzysta z oddzielnych granic Suspense, więc szybkie widgety pojawiają się natychmiast.
  • Granice błędów powyżej Suspense: Zawsze należy opakowywać Suspense obsługą błędów. Awarie sieci i błędy API nie mogą crashować całego drzewa.
  • Timing przejść: Przy łączeniu Suspense z Transition, należy ustawić mode="out-in" aby zapobiec nakładaniu się podczas zmian stanu.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Wyzwanie dnia

Znajdziesz błąd w Vue.js / Nuxt.js?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 28 sierpnia 2026

Tagi

#vue 3
#teleport
#suspense
#vue zaawansowane
#pytania rekrutacyjne

Udostępnij

Powiązane artykuły