비동기 컴포넌트
기본 사용법
대형 애플리케이션에서는 앱을 더 작은 청크로 나누고, 필요할 때만 서버에서 컴포넌트(component)를 로드해야 할 수 있습니다. 이를 위해 Vue는 defineAsyncComponent 함수를 제공합니다:
js
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...서버에서 컴포넌트 로드
resolve(/* 로드된 컴포넌트 */)
})
})
// ... `AsyncComp`을 일반 컴포넌트처럼 사용보시다시피, defineAsyncComponent는 Promise를 반환하는 로더 함수를 인자로 받습니다. 서버에서 컴포넌트 정의를 가져왔을 때 Promise의 resolve 콜백(callback)을 호출해야 합니다. 로드에 실패했음을 나타내려면 reject(reason)을 호출할 수도 있습니다.
ES 모듈 동적 import도 Promise를 반환하므로, 대부분의 경우 defineAsyncComponent와 함께 사용합니다. Vite와 webpack 같은 번들러(bundler)도 이 문법을 지원하며(번들 분할 지점으로 사용), 이 문법으로 Vue SFC를 import할 수 있습니다:
js
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)결과로 생성된 AsyncComp는 실제로 페이지에 렌더링(rendering)될 때만 로더 함수를 호출하는 래퍼 컴포넌트입니다. 또한, 모든 props와 slot을 내부 컴포넌트로 전달하므로, 비동기 래퍼를 사용해 원래 컴포넌트를 무리 없이 대체하면서 지연 로딩(lazy loading)을 구현할 수 있습니다.
일반 컴포넌트와 마찬가지로, 비동기 컴포넌트도 app.component()를 사용해 전역 등록할 수 있습니다:
js
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))부모 컴포넌트 내부에서 직접 정의할 수도 있습니다:
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,
// 로딩 컴포넌트를 표시하기 전의 지연 시간. 기본값: 200ms.
delay: 200,
// 로드에 실패했을 때 사용할 컴포넌트
errorComponent: ErrorComponent,
// 타임아웃이 지정되고 초과되면 에러 컴포넌트가 표시됩니다.
// 기본값: Infinity.
timeout: 3000
})로딩 컴포넌트가 제공되면, 내부 컴포넌트가 로드되는 동안 먼저 표시됩니다. 로딩 컴포넌트가 표시되기 전 기본 200ms의 지연이 있는데, 이는 빠른 네트워크에서는 즉시 표시된 로딩 상태가 너무 빨리 대체되어 깜빡임처럼 보일 수 있기 때문입니다.
에러 컴포넌트가 제공되면, 로더 함수가 반환한 Promise가 거부(reject)될 때 표시됩니다. 요청이 너무 오래 걸릴 때 에러 컴포넌트를 표시하도록 타임아웃을 지정할 수도 있습니다.
지연 하이드레이션
이 섹션은 서버 사이드 렌더링을 사용하는 경우에만 적용됩니다.
Vue 3.5+에서는 비동기 컴포넌트가 하이드레이션(hydration) 전략을 제공하여 언제 하이드레이션할지 제어할 수 있습니다.
Vue는 여러 내장 하이드레이션 전략을 제공합니다. 이 내장 전략들은 사용하지 않을 경우 트리 셰이킹(tree-shaking)이 가능하도록 개별적으로 import해야 합니다.
설계는 유연성을 위해 의도적으로 저수준으로 되어 있습니다. 향후 코어 또는 Vue 프레임워크와 같은 상위 레벨 솔루션에서 컴파일러 문법 설탕이 추가될 수 있습니다.
Idle 시 하이드레이션
requestIdleCallback을 통해 하이드레이션합니다:
js
import { defineAsyncComponent, hydrateOnIdle } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnIdle(/* 선택적으로 최대 타임아웃 전달 가능 */)
})보일 때 하이드레이션
IntersectionObserver를 통해 요소가 보일 때 하이드레이션합니다.
js
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible()
})옵저버를 위한 옵션 객체 값을 선택적으로 전달할 수 있습니다:
js
hydrateOnVisible({ rootMargin: '100px' })미디어 쿼리로 하이드레이션
지정한 미디어 쿼리가 일치할 때 하이드레이션합니다.
js
import { defineAsyncComponent, hydrateOnMediaQuery } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnMediaQuery('(max-width:500px)')
})상호작용 시 하이드레이션
지정한 이벤트가 컴포넌트 요소에서 발생할 때 하이드레이션합니다. 하이드레이션이 완료되면 트리거된 이벤트도 재생됩니다.
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> 전용 챕터에 문서화되어 있습니다.