Перейти к содержанию

Асинхронные компоненты

Пример использования

В больших приложениях нам может понадобиться разделить приложение на более мелкие фрагменты и загружать компонент с сервера только тогда, когда он необходим. Чтобы сделать это возможным, во Vue есть функция defineAsyncComponent:

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...загрузка компонента с сервера
    resolve(/* загружаемый компонент */)
  })
})
// ... используем `AsyncComp` как обычный компонент

Как видите, defineAsyncComponent принимает функцию-загрузчик, которая возвращает Promise. Обратный вызов Promise resolve должен быть вызван, когда вы получили определение компонента с сервера. Вы также можете вызвать reject(reason), чтобы указать, что загрузка не удалась.

Динамический импорт модулей ES также возвращает Promise, поэтому чаще всего мы будем использовать его в сочетании с defineAsyncComponent. Такие сборщики, как Vite и webpack, также поддерживают этот синтаксис (и будут использовать его в качестве точек разделения сборки), поэтому мы можем использовать его для импорта Vue SFC:

js
import { defineAsyncComponent } from 'vue'

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

Получившийся AsyncComp — это компонент-обёртка, который вызывает функцию загрузчика только тогда, когда он действительно отображается на странице. Кроме того, он будет передавать все параметры и слоты внутреннему компоненту, так что вы можете использовать асинхронную обёртку для плавной замены исходного компонента, обеспечивая при этом ленивую загрузку.

Как и обычные компоненты, асинхронные компоненты могут быть зарегистрированы глобально с помощью app.component():

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

Вы также можете использовать defineAsyncComponent при локальной регистрации компонента:

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

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

<template>
  <AdminPage />
</template>

Они также могут быть определены непосредственно внутри родительского компонента:

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

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

<template>
  <AdminPage />
</template>

Состояния загрузки и ошибки

Асинхронные операции неизбежно влекут за собой состояния загрузки и ошибки — defineAsyncComponent() поддерживает обработку этих состояний с помощью дополнительных опций:

js
const AsyncComp = defineAsyncComponent({
  // функция-загрузчик
  loader: () => import('./Foo.vue'),

  // Компонент, который будет использоваться во время загрузки асинхронного компонента
  loadingComponent: LoadingComponent,
  // Задержка перед показом компонента загрузки. По умолчанию: 200 мс.
  delay: 200,

  // Компонент, который будет использоваться в случае сбоя загрузки
  errorComponent: ErrorComponent,
  // Компонент ошибки будет отображен, если тайм-аут указан и превышен. По умолчанию: Infinity.
  timeout: 3000
})

Если предоставлен загрузочный компонент, он будет отображаться первым, пока загружается внутренний компонент. По умолчанию существует задержка в 200 мс перед отображением компонента загрузки — это связано с тем, что в быстрых сетях состояние мгновенной загрузки может сменяться слишком быстро и в итоге выглядеть как мерцание.

Если указан компонент ошибки, он будет отображаться, когда Promise, возвращённый функцией загрузчика, будет отклонён. Вы также можете указать тайм-аут для отображения компонента ошибки, если запрос выполняется слишком долго.

Ленивая гидратация

Этот раздел применим только при использовании рендеринга на стороне сервера.

В Vue 3.5+ асинхронные компоненты могут контролировать время гидратации, предоставляя стратегию гидратации.

  • Vue предоставляет несколько встроенных стратегий гидратации. Эти встроенные стратегии необходимо импортировать по отдельности, чтобы их можно было вытеснить из дерева, если они не используются.

  • Дизайн намеренно низкоуровневый для обеспечения гибкости. Синтаксический сахар компилятора потенциально может быть построен на основе этого в будущем либо в ядре, либо в решениях более высокого уровня (например, в Nuxt).

Гидратация на холостом ходу

Запуск hydrate через requestIdleCallback:

js
import { defineAsyncComponent, hydrateOnIdle } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate:
    hydrateOnIdle(/* опциональная передача максимального тайм-аута */)
})

Гидратация на видимом элементе

Запуск hydrate, когда элемент(ы) становятся видимыми через IntersectionObserver.

js
import { defineAsyncComponent, hydrateOnVisible } from 'vue'

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

В качестве опции можно передать значение объекта options для наблюдателя:

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

Гидратация при медиазапросе

Запуск hydrate при совпадении указанного медиазапроса.

js
import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'

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

Гидратация при взаимодействии

Запуск hydrate при наступлении указанного события (событий) на элементе (элементах) компонента. Событие, вызвавшее гидратацию, также будет воспроизведено после завершения гидратации.

js
import { defineAsyncComponent, hydrateOnInteraction } from 'vue'

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

Также может быть списком нескольких типов событий:

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

Индивидуальная стратегия

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

const myStrategy: HydrationStrategy = (hydrate, forEachElement) => {
  // forEachElement - это помощник для перебора всех корневых элементов
  // в негидратированном DOM компонента, поскольку корневой элемент может быть фрагментом
  // вместо одного элемента
  forEachElement((el) => {
    // ...
  })
  // вызываем `hydrate` при готовности
  hydrate()
  return () => {
    // при необходимости возвращаем функцию разрушения
  }
}

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

Использование с Suspense

Асинхронные компоненты можно использовать со встроенным компонентом <Suspense>. Взаимодействие между <Suspense> и асинхронными компонентами описано в главе <Suspense>.

Асинхронные компоненты