Saltar al contenido

Componentes Asíncronos

Uso Básico

En aplicaciones grandes, puede que necesitemos dividir la aplicación en partes más pequeñas y cargar un componente desde el servidor solo cuando sea necesario. Para hacer esto posible, Vue tiene una función defineAsyncComponent:

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...cargar componente desde el servidor
    resolve(/* componente cargado */)
  })
})
// ...usar `AsyncComp` como un componente normal

Como puedes ver, defineAsyncComponent acepta una función cargadora que devuelve una Promise. La función de callback resolve de la Promise debe ser llamada cuando hayas recuperado la definición de tu componente del servidor. También puedes llamar a reject(reason) para indicar que la carga ha fallado.

La importación dinámica de módulos ES también devuelve una Promise, así que la mayoría de las veces la usaremos en combinación con defineAsyncComponent. Bundlers como Vite y webpack también soportan esta sintaxis (y la usarán como puntos de división de bundle), así que podemos usarla para importar SFCs de Vue:

js
import { defineAsyncComponent } from 'vue'

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

El AsyncComp resultante es un componente envoltorio que solo llama a la función cargadora cuando se renderiza realmente en la página. Además, pasará cualquier props y slots al componente interno, por lo que puedes usar el envoltorio asíncrono para reemplazar sin problemas el componente original mientras logras la carga perezosa.

Al igual que con los componentes normales, los componentes asíncronos pueden ser registrados globalmente usando app.component():

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

También puedes usar defineAsyncComponent cuando registras un componente localmente:

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

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

<template>
  <AdminPage />
</template>

También pueden definirse directamente dentro de su componente padre:

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

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

<template>
  <AdminPage />
</template>

Estados de Carga y Error

Las operaciones asíncronas inevitablemente implican estados de carga y error - defineAsyncComponent() soporta el manejo de estos estados a través de opciones avanzadas:

js
const AsyncComp = defineAsyncComponent({
  // la función de carga
  loader: () => import('./Foo.vue'),

  // Un componente a usar mientras se carga el componente asíncrono
  loadingComponent: LoadingComponent,
  // Retraso antes de mostrar el componente de carga. Por defecto: 200ms.
  delay: 200,

  // Un componente a usar si falla la carga
  errorComponent: ErrorComponent,
  // El componente de error se mostrará si se proporciona un
  // tiempo de espera y se supera. Por defecto: Infinito.
  timeout: 3000
})

Si se proporciona un componente de carga, este se mostrará primero mientras se carga el componente interno. Hay un retraso predeterminado de 200ms antes de que se muestre el componente de carga - esto se debe a que en redes rápidas, un estado de carga instantáneo podría ser reemplazado demasiado rápido y terminar pareciendo un parpadeo.

Si se proporciona un componente de error, este se mostrará cuando la Promise devuelta por la función cargadora sea rechazada. También puedes especificar un timeout para mostrar el componente de error cuando la solicitud está tardando demasiado.

Hidratación Perezosa

Esta sección solo aplica si estás usando Server-Side Rendering.

En Vue 3.5+, los componentes asíncronos pueden controlar cuándo se hidratan al proporcionar una estrategia de hidratación.

  • Vue proporciona varias estrategias de hidratación incorporadas. Estas estrategias incorporadas deben importarse individualmente para que puedan ser tree-shaken si no se utilizan.

  • El diseño es intencionadamente de bajo nivel para mayor flexibilidad. El syntax sugar del compilador puede construirse sobre esto en el futuro, ya sea en el core o en soluciones de nivel superior (por ejemplo, Nuxt).

Hidratar en Inactividad

Hidrata vía requestIdleCallback:

js
import { defineAsyncComponent, hydrateOnIdle } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate:
    hydrateOnIdle(/* opcionalmente, pasar un tiempo de espera máximo */)
})

Hidratar al ser Visible

Hidrata cuando el/los elemento(s) se vuelven visibles vía IntersectionObserver.

js
import { defineAsyncComponent, hydrateOnVisible } from 'vue'

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

Opcionalmente se puede pasar un objeto de opciones para el observador:

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

Hidratar por Media Query

Hidrata cuando la media query especificada coincide.

js
import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'

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

Hidratar en Interacción

Hidrata cuando el/los evento(s) especificado(s) se activan en el/los elemento(s) del componente. El evento que desencadenó la hidratación también se reproducirá una vez que la hidratación esté completa.

js
import { defineAsyncComponent, hydrateOnInteraction } from 'vue'

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

También puede ser una lista de varios tipos de eventos:

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

Estrategia Personalizada

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

const myStrategy: HydrationStrategy = (hydrate, forEachElement) => {
  // forEachElement es un helper para iterar a través de todos los
  // elementos raíz en el DOM no hidratado del componente, ya que
  // la raíz puede ser un fragmento en lugar de un solo elemento
  forEachElement((el) => {
    // ...
  })
  // llama a `hydrate` cuando esté listo
  hydrate()
  return () => {
    // devuelve una función de desmontaje si es necesario
  }
}

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

Uso con Suspense

Los componentes asíncronos pueden usarse con el componente integrado <Suspense>. La interacción entre <Suspense> y los componentes asíncronos está documentada en el capítulo dedicado a <Suspense>.

Componentes Asíncronos