Vue 3 中 defineAsyncComponent 异步组件

FreeGuideOnline 最新 2026-07-07

**注意:** 与 Vue 2 不同,Vue 3 中不再需要在组件注册时直接将异步函数赋值给组件选项,而是统一使用 `defineAsyncComponent`。

---

## 高级选项:控制加载与错误状态

`defineAsyncComponent` 还支持一个选项对象,用于更精细地控制加载行为、错误处理和超时。

```javascript
const AsyncComp = defineAsyncComponent({
  // 加载工厂函数
  loader: () => import('./HeavyComponent.vue'),

  // 加载中时显示的组件
  loadingComponent: {
    template: '<div class="loading-spinner">加载中...</div>'
  },

  // 加载失败时显示的组件
  errorComponent: {
    template: '<div class="error-box">组件加载失败,请刷新重试</div>'
  },

  // 显示加载组件前的延迟时间(毫秒)。如果加载快于该时间,则不会显示 loading
  delay: 200,

  // 超时时间(毫秒)。超时后将显示错误组件
  timeout: 3000,

  // 定义组件是否可挂起(与 Suspense 相关),默认为 true
  suspensible: true,

  // 错误处理回调:可在此重试或记录日志
  onError(error, retry, fail, attempts) {
    // retry 是一个函数,调用后会重新尝试加载
    // fail 用于放弃加载,显示错误组件
    if (error.message.includes('fetch') && attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})

配置项详解

选项 类型 说明
loader () => Promise<Component> 返回组件定义的工厂函数
loadingComponent Component 异步加载过程中显示的占位组件
errorComponent Component 加载失败后展示的回退组件
delay number 延迟展示 loadingComponent 的毫秒数,避免闪烁
timeout number 超时时间,超时后直接显示 errorComponent
suspensible boolean 是否支持与 <Suspense> 集成
onError (error, retry, fail, attempts) => void 错误处理钩子,可实现重试逻辑

实战示例:带加载指示的异步表格组件

假设我们要创建一个数据量较大的表格组件,希望它只在用户访问该页面时才加载。

1. 异步表格组件 DataTable.vue

<template>
  <table>
    <thead>
      <tr><th>ID</th><th>名称</th></tr>
    </thead>
    <tbody>
      <tr v-for="item in data" :key="item.id">
        <td>{{ item.id }}</td>
        <td>{{ item.name }}</td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
defineProps({
  data: Array
})
</script>

2. 定义异步组件并处理加载状态

<template>
  <div>
    <h2>用户列表</h2>
    <AsyncTable :data="userList" />
  </div>
</template>

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

const userList = ref([
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
])

const AsyncTable = defineAsyncComponent({
  loader: () => import('./DataTable.vue'),
  loadingComponent: {
    template: '<p class="text-gray">表格加载中...</p>'
  },
  errorComponent: {
    template: '<p class="text-red">加载失败,请稍后重试</p>'
  },
  delay: 300,
  timeout: 5000
})
</script>

<Suspense> 配合使用

当异步组件内部存在异步依赖(如 async setup()top-level await)时,<Suspense> 可以优雅地协调多个异步操作的加载状态。

<template>
  <Suspense>
    <!-- 默认插槽:异步组件 -->
    <AsyncDashboard />

    <!-- fallback 插槽:加载中占位 -->
    <template #fallback>
      <div class="spinner">仪表板加载中...</div>
    </template>
  </Suspense>
</template>

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

const AsyncDashboard = defineAsyncComponent(() =>
  import('./Dashboard.vue')
)
</script>

关键点:

  • AsyncDashboard 内如果包含 awaitsetup<Suspense> 会捕获其状态并显示 fallback 内容。
  • 若设置了 suspensible: false,该组件将不受外部 <Suspense> 控制,转而使用自身的 loading/error 逻辑。

常见问题与最佳实践

1. 如何实现加载失败后的重试?

onError 钩子中利用 retry 函数,可以方便地实现有限次数的重试。

onError(error, retry, fail, attempts) {
  if (attempts < 2) {
    console.log(`第 ${attempts + 1} 次重试...`)
    retry()
  } else {
    fail()
  }
}

2. 异步组件可以用在 v-for 内部吗?

可以,但每个组件实例都会独立加载,可能触发大量网络请求。如果列表项相同,建议在父级使用同一个异步组件引用。

3. 如何预加载异步组件?

在用户即将访问时(如鼠标悬停)提前加载。

const loadComponent = () => import('./MyComponent.vue')

// 预加载
onMounted(() => {
  loadComponent()
})

// 定义异步组件
const AsyncComp = defineAsyncComponent(() => loadComponent())