Passer au contenu

Composants asynchrones

Utilisation de base

Dans des applications de taille importante, il est parfois judicieux de diviser l'application en plus petits morceaux et ne charger un composant depuis le serveur que lorsque cela est nécessaire. Pour rendre cela possible, Vue a une fonction defineAsyncComponent :

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...charger le composant depuis le serveur
    resolve(/* composant chargé */)
  })
})
// ... utiliser `AsyncComp` comme un composant normal

Comme vous pouvez le voir, defineAsyncComponent accepte une fonction de chargement qui renvoie une promesse (Promise). La fonction resolve de la promesse doit être appelée lorsque vous avez récupéré la définition de votre composant à partir du serveur. Vous pouvez également appeler reject(reason) pour indiquer que le chargement a échoué.

L'import dynamique des modules ES renvoie également une promesse, ainsi la plupart du temps, nous l'utiliserons en combinaison avec defineAsyncComponent. Les Bundlers comme Vite et webpack prennent également en charge la syntaxe (et l'utiliseront comme méthode de séparation du bundle), nous pouvons donc l'utiliser pour importer des SFC Vue :

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

L'AsyncComp résultant est un composant wrapper qui n'appelle la fonction de chargement que lorsque le composant est réellement rendu sur la page. De plus, il transmettra tous les props et slots au composant sous-jacent, de sorte que vous pouvez utiliser le wrapper asynchrone pour remplacer de manière transparente le composant d'origine tout en réalisant un lazy loading.

Comme pour les composants normaux, les composants asynchrones peuvent être enregistrés globalement à l'aide de app.component() :

js
app.component(
  'MyComponent',
  defineAsyncComponent(() => import('./components/MyComponent.vue'))
)

Vous pouvez également utiliser defineAsyncComponent lors de l'enregistrement local d'un composant :

vue
<script>
import { defineAsyncComponent } from 'vue'

export default {
  components: {
    AdminPage: defineAsyncComponent(() =>
      import('./components/AdminPageComponent.vue')
    )
  }
}
</script>

<template>
  <AdminPage />
</template>

Ils peuvent également être définis directement à l'intérieur de leur composant parent :

vue
<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)
</script>

<template>
  <AdminPage />
</template>

Etats de chargement et d'erreur

Les opérations asynchrones impliquent inévitablement des états de chargement et d'erreur - defineAsyncComponent() prend en charge la gestion de ces états via des options avancées :

js
const AsyncComp = defineAsyncComponent({
  // la fonction de chargememnt
  loader: () => import('./Foo.vue'),

  // Un composant à utiliser pendant le chargement du composant asynchrone
  loadingComponent: LoadingComponent,
  // Délai avant l'affichage du composant de chargement. Par défaut : 200 ms.
  delay: 200,

  // Un composant à utiliser si le chargement échoue
  errorComponent: ErrorComponent,
  // Le composant d'erreur sera affiché si un délai d'attente est
  // fourni et dépassé. Par défaut : Infini.
  timeout: 3000
})

Si un composant de chargement est fourni, il sera affiché en premier lors du chargement du composant sous-jacent. Il y a un délai par défaut de 200 ms avant que le composant de chargement ne s'affiche - car sur les réseaux rapides, un état de chargement instantané peut être remplacé trop rapidement et finir par apparaitre comme un effet de scintillement à l'écran.

Si un composant d'erreur est fourni, il sera affiché lorsque la promesse renvoyée par la fonction de chargement est rejetée. Vous pouvez également spécifier un délai d'expiration pour afficher le composant d'erreur lorsque la demande prend trop de temps.

Lazy Hydration

This section only applies if you are using Server-Side Rendering.

In Vue 3.5+, async components can control when they are hydrated by providing a hydration strategy.

  • Vue provides a number of built-in hydration strategies. These built-in strategies need to be individually imported so they can be tree-shaken if not used.

  • The design is intentionally low-level for flexibility. Compiler syntax sugar can potentially be built on top of this in the future either in core or in higher level solutions (e.g. Nuxt).

Hydrate on Idle

Hydrates via requestIdleCallback:

js
import { defineAsyncComponent, hydrateOnIdle } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: hydrateOnIdle(/* optionally pass a max timeout */)
})

Hydrate on Visible

Hydrate when element(s) become visible via IntersectionObserver.

js
import { defineAsyncComponent, hydrateOnVisible } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: hydrateOnVisible()
})

Can optionally pass in an options object value for the observer:

js
hydrateOnVisible({ rootMargin: '100px' })

Hydrate on Media Query

Hydrates when the specified media query matches.

js
import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: hydrateOnMediaQuery('(max-width:500px)')
})

Hydrate on Interaction

Hydrates when specified event(s) are triggered on the component element(s). The event that triggered the hydration will also be replayed once hydration is complete.

js
import { defineAsyncComponent, hydrateOnInteraction } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: hydrateOnInteraction('click')
})

Can also be a list of multiple event types:

js
hydrateOnInteraction(['wheel', 'mouseover'])

Custom Strategy

ts
import { defineAsyncComponent, type HydrationStrategy } from 'vue'

const myStrategy: HydrationStrategy = (hydrate, forEachElement) => {
  // forEachElement is a helper to iterate through all the root elements
  // in the component's non-hydrated DOM, since the root can be a fragment
  // instead of a single element
  forEachElement(el => {
    // ...
  })
  // call `hydrate` when ready
  hydrate()
  return () => {
    // return a teardown function if needed
  }
}

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: myStrategy
})

Utilisation avec Suspense

Les composants asynchrones peuvent être utilisés avec le composant fourni <Suspense>. L'interaction entre <Suspense> et les composants asynchrones est documentée dans le chapitre dédié à <Suspense>.

Composants asynchronesa chargé