Vue 3 Teleport e Suspense: Pattern Avanzati e Domande di Colloquio 2026

Padroneggia Vue 3 Teleport e Suspense con pattern avanzati, la prop defer e domande pratiche di colloquio per sviluppatori Vue senior.

Vue 3 Teleport e Suspense: Pattern Avanzati e Domande di Colloquio 2026

Vue 3 Teleport e Suspense risolvono due sfide di rendering distinte: posizionare componenti al di fuori della loro gerarchia DOM e coordinare dipendenze asincrone. Entrambi sono componenti built-in che non richiedono import, ed entrambi compaiono frequentemente nei colloqui per sviluppatori Vue senior.

Aggiornamento Teleport Vue 3.5

Vue 3.5 ha aggiunto la prop defer a Teleport, permettendo target renderizzati successivamente nell'albero dei componenti. Questo risolve il comune problema di timing SSR dove il target del portale non esiste al momento del mount.

Fondamenti di Teleport e la Prop defer

Teleport sposta il contenuto renderizzato in una posizione DOM diversa preservando l'albero dei componenti Vue. Il componente rimane un figlio logico del suo parent: props, eventi e provide/inject funzionano normalmente. Vue DevTools mostra il componente teletrasportato annidato sotto il suo parent, non sotto l'elemento target.

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>

La prop to accetta una stringa selettore CSS o un riferimento a un elemento DOM. Quando il target non esiste al momento del mount, Teleport emette un warning e non renderizza nulla. Questo crea problemi con SSR e layout dinamici.

La Prop defer (Vue 3.5+)

La prop defer indica a Teleport di attendere che altre parti dell'applicazione siano montate prima di risolvere il target. Questo permette di teletrasportare verso elementi renderizzati successivamente nello stesso componente o in componenti fratelli.

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

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

Senza defer, questo codice fallisce perché #dynamic-target non esiste quando Teleport viene montato. Con defer, Vue risolve il target dopo che tutti i componenti nello stesso tick sono stati montati. Il target deve comunque renderizzare nello stesso ciclo di mount o update.

Teleport Multipli e Rendering Condizionale

Più componenti Teleport possono avere lo stesso elemento come target. Il contenuto viene aggiunto nell'ordine di dichiarazione:

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

La prop disabled controlla se la teleportazione avviene. Quando disabilitata, il contenuto viene renderizzato inline. Questo pattern gestisce layout responsive dove i modal devono sovrapporsi su desktop ma renderizzare inline su mobile:

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 per l'Orchestrazione di Componenti Asincroni

Suspense coordina le dipendenze asincrone attraverso un albero di componenti. Quando un qualsiasi componente discendente ha una dipendenza asincrona non risolta, Suspense mostra il contenuto dello slot fallback. Una volta che tutte le dipendenze sono risolte, Suspense passa allo slot default.

Stato Sperimentale

Suspense rimane sperimentale in Vue 3.5. L'API potrebbe cambiare prima di raggiungere lo stato stabile. L'uso in produzione richiede l'accettazione di questo rischio.

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

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

Suspense traccia due tipi di dipendenze asincrone: componenti con async setup() e componenti asincroni creati con defineAsyncComponent.

Async Setup con Top-Level Await

Vue 3 script setup supporta il top-level await. Qualsiasi componente che usa top-level await diventa una dipendenza asincrona che Suspense può tracciare:

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>

Questo componente non può renderizzare finché il fetch non è completato. Un boundary Suspense padre mostra il fallback fino alla risoluzione. Senza un boundary Suspense, il componente semplicemente non viene renderizzato.

Suspense Annidato con la Prop suspensible

Le applicazioni complesse hanno spesso più boundary asincroni. Vue 3.3 ha introdotto la prop suspensible per controllare come i componenti Suspense annidati interagiscono con i loro parent.

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>

Quando suspensible è impostato, il Suspense interno si registra con il boundary Suspense parent. Il parent attende che sia AsyncNavigation che AsyncContent siano risolti prima di nascondere il suo fallback. Il Suspense interno serve comunque come boundary locale: se AsyncContent impiega più tempo, ContentSkeleton viene mostrato mentre AsyncNavigation rimane visibile.

Senza suspensible, il Suspense interno opera indipendentemente. Il parent mostra il suo fallback solo mentre AsyncNavigation carica, ignorando completamente AsyncContent.

Pronto a superare i tuoi colloqui su Vue.js / Nuxt.js?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Combinare Teleport, Suspense e Transitions

Le applicazioni reali combinano questi pattern. Un modal che carica contenuto in modo asincrono beneficia di tutti e tre:

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>

L'ordine di annidamento è importante: Teleport avvolge Transition, che avvolge l'elemento condizionale, che contiene Suspense. Questo assicura che la transizione animi l'intero modal inclusi gli stati di caricamento.

Gestione degli Errori con Suspense

Suspense non gestisce gli errori. Le dipendenze asincrone fallite rifiutano le loro promise e propagano gli errori su per l'albero dei componenti. L'hook del ciclo di vita onErrorCaptured cattura questi errori al boundary:

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>

Questo pattern avvolge qualsiasi albero di componenti asincroni con stati sia di caricamento che di errore. Il pulsante retry cancella l'errore, causando il re-render di Vue e un nuovo tentativo dell'operazione asincrona.

Domande di Colloquio su Teleport e Suspense

Le posizioni Vue senior testano sempre più spesso questi pattern. Domande comuni e cosa distingue le risposte forti:

D: Quando dovrebbe essere usato Teleport invece del posizionamento CSS?

Risposta debole: "Quando z-index non funziona."

Risposta forte: Teleport risolve problemi di gerarchia DOM che CSS non può risolvere. Un modal dentro un container con overflow: hidden o transform crea un nuovo stacking context, rompendo position: fixed. Teleport sposta il modal nel body, sfuggendo a questi vincoli mentre preserva l'albero logico dei componenti Vue.

D: Come differisce Suspense dagli stati di caricamento in ogni componente?

Risposta debole: "Suspense è più pulito."

Risposta forte: Suspense coordina multiple dipendenze asincrone. Dieci componenti con stati di caricamento individuali mostrano dieci spinner in momenti diversi. Suspense mostra uno spinner finché tutti e dieci sono risolti, poi li rivela insieme. Questo crea una performance percepita più fluida e codice dei componenti più semplice poiché i componenti asincroni non devono gestire i propri stati di caricamento.

D: Cosa succede quando un target Teleport non esiste?

Il contenuto non viene renderizzato e Vue registra un warning. La prop defer di Vue 3.5 affronta questo aspetto attendendo che altri componenti siano montati prima. Senza defer, assicurarsi che i target esistano in index.html o in un componente parent che viene montato prima dei children.

D: Suspense può funzionare con l'Options API?

Sì. Qualsiasi componente con async setup() attiva Suspense, indipendentemente dal fatto che usi Composition API o Options API altrove. La funzione setup è il boundary asincrono, non lo stile del componente.

Teleport e Suspense in Nuxt 3

Nuxt 3 estende questi pattern con considerazioni SSR. La documentazione Nuxt sul rendering copre il timing dell'idratazione.

Per Teleport, Nuxt richiede che il target esista prima dell'idratazione. I target dei portali possono essere aggiunti in app.vue o si può usare la prop defer:

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

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

Per Suspense, useFetch e useAsyncData di Nuxt si integrano automaticamente. I componenti pagina che usano questi composables funzionano con il Suspense built-in di Nuxt senza configurazione manuale.

Pattern di Produzione per Vue 3.5

  • Target Teleport in index.html: Creare target portal stabili che esistono prima che qualsiasi codice Vue venga eseguito. Questo evita completamente i problemi di timing.
  • Boundary Suspense Granulari: Un Suspense per unità di caricamento indipendente. Una dashboard con widget separati beneficia di boundary Suspense separati così i widget veloci appaiono immediatamente.
  • Error Boundary sopra Suspense: Avvolgere sempre Suspense con gestione degli errori. Fallimenti di rete ed errori API non devono crashare l'intero albero.
  • Timing delle Transition: Quando si combina Suspense con Transition, impostare mode="out-in" per prevenire sovrapposizioni durante i cambi di stato.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Sfida del giorno

Sapresti trovare il bug in Vue.js / Nuxt.js?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 28 agosto 2026

Condividi

Articoli correlati