Skip to content

在 Vue 3 中,异步组件(Async Components)和 <Suspense> 组件是实现优雅的异步依赖加载与用户体验的核心搭档。它们共同协作,让你能够懒加载组件、处理加载状态、管理错误,而无需在每个组件内部手动编写复杂的逻辑。


一、异步组件:defineAsyncComponent

defineAsyncComponent 是 Vue 提供的工厂函数,用于定义一个异步加载的组件。它接收一个返回 Promise 的加载函数。

1. 基本用法

js
import { defineAsyncComponent } from 'vue';

// 懒加载一个组件
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
);

// 在模板中使用
<template>
  <AsyncComponent />
</template>

2. 加载函数的返回值

加载函数必须返回一个 Promise,该 Promiseresolve 一个符合组件规范的对象:

js
const AsyncComponent = defineAsyncComponent(() =>
  Promise.resolve({
    template: '<div>I am async!</div>'
  })
);

通常配合 import() 动态导入使用,Webpack/Rollup 会自动将其打包为独立的 chunk。


二、<Suspense>:异步依赖的“协调者”

<Suspense> 是一个内置的高阶组件,它有两个插槽:

  • #default:包含异步依赖(如异步组件)。
  • #fallback:在异步依赖加载时显示的备用内容。

基本结构

vue
<template>
  <Suspense>
    <!-- 主要内容 -->
    <template #default>
      <AsyncComponent />
      <AnotherAsyncComponent />
    </template>

    <!-- 加载时显示 -->
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

三、协作机制:深度解析

<Suspense>defineAsyncComponent 的协作是一个精妙的“等待-解析-激活”过程。

1. 初始化阶段

  • <Suspense> 被渲染时,它会立即渲染 #fallback 插槽的内容。
  • 同时,它会递归遍历 #default 插槽中的所有组件,查找“挂起的”(pending)异步依赖。

2. 检测异步依赖

  • defineAsyncComponent 创建的组件在首次渲染时,其加载函数(import())会返回一个 Promise
  • 这个组件进入“挂起”(pending)状态。
  • <Suspense> 检测到这个挂起的组件,并将其加入“依赖列表”。

3. 等待与解析

  • <Suspense> 等待其依赖列表中的所有 Promise 都被 resolve
  • 如果有多个异步组件,它会等待最慢的那个完成(“慢者决定”)。
  • 一旦所有依赖都加载完成,<Suspense> 触发切换。

4. 切换到主内容

  • <Suspense> 停止渲染 #fallback
  • 开始渲染 #default 插槽中的内容
  • 此时,异步组件已被解析,defineAsyncComponent 会返回其实际的组件定义,正常渲染。

5. 错误处理

  • 如果任何一个异步依赖的 Promisereject<Suspense> 会抛出错误。
  • 你可以使用 errorCaptured 钩子或 Error Boundary 组件来捕获和处理。

四、defineAsyncComponent 的高级配置

defineAsyncComponent 可以接收一个配置对象,提供更多控制:

js
const AsyncComponent = defineAsyncComponent({
  // 异步加载函数
  loader: () => import('./HeavyComponent.vue'),

  // 加载延迟:在显示 fallback 前等待 200ms
  delay: 200,

  // 超时时间:10秒后加载失败
  timeout: 10000,

  // 错误处理
  errorComponent: ErrorComponent,
  onError(error, retry, fail, attempts) {
    if (error.message.includes('timeout')) {
      fail(); // 直接失败
    } else if (attempts < 3) {
      retry(); // 重试
    } else {
      fail(); // 放弃
    }
  }
});
  • delay:避免短暂的加载闪烁。如果组件在 delay 时间内加载完成,则不显示 fallback
  • timeout:防止无限等待。
  • errorComponent:加载失败时显示的组件。
  • onError:自定义错误处理逻辑,支持重试。

五、<Suspense> 的生命周期钩子

<Suspense> 提供了两个事件,让你可以监听其状态变化:

vue
<Suspense
  @resolve="onResolve"
  @pending="onPending"
>
  <template #default>...</template>
  <template #fallback>...</template>
</Suspense>
  • @pending

    • 在进入等待状态时触发(即开始显示 fallback)。
    • 可用于重置状态、启动加载指示器。
  • @resolve

    • 当所有异步依赖加载完成,即将切换到 #default 时触发。
    • 可用于执行切换动画、记录性能等。

六、实战场景

1. 路由级别的懒加载

js
// router.js
const routes = [
  {
    path: '/dashboard',
    component: defineAsyncComponent(() =>
      import('../views/Dashboard.vue')
    )
  }
];
vue
<!-- App.vue -->
<template>
  <RouterView v-slot="{ Component }">
    <Suspense>
      <component :is="Component" />
      <template #fallback>
        <PageLoading />
      </template>
    </Suspense>
  </RouterView>
</template>

2. 组件级别的数据预加载

js
// AsyncUserProfile.vue
const AsyncUserProfile = defineAsyncComponent(async () => {
  const userData = await fetchUser();
  const postsData = await fetchPosts(userData.id);
  
  return {
    setup() {
      return { userData, postsData };
    },
    template: `
      <div>
        <h1>{{ userData.name }}</h1>
        <ul>
          <li v-for="post in postsData" :key="post.id">{{ post.title }}</li>
        </ul>
      </div>
    `
  };
});

<Suspense> 会等待 userDatapostsData 都获取完成后再显示内容。


七、注意事项

  1. 仅对异步组件有效

    • <Suspense> 只能检测 defineAsyncComponentsetup 返回 Promise 的组件。
    • 普通的 fetch 请求不会被 <Suspense> 捕获。
  2. 嵌套 <Suspense>

    • 可以嵌套使用,内层的 <Suspense> 不会影响外层。
  3. keep-alive 协作

    • 可以包裹 <Suspense>,缓存已加载的异步组件。
  4. SSR 支持

    • Vue 3 的 SSR 也支持 <Suspense>,服务端会等待异步组件解析后再发送 HTML。

八、总结

defineAsyncComponent<Suspense> 的协作机制可以概括为:

  1. defineAsyncComponent 负责“制造异步依赖”,将组件的加载延迟到运行时。
  2. <Suspense> 负责“协调异步过程”,管理加载状态、错误和内容切换。

它们共同实现了:

  • 代码分割:减少首屏加载体积。
  • 用户体验优化:提供加载反馈,避免白屏。
  • 开发体验提升:声明式地处理异步,逻辑清晰。

掌握这对组合,你就能构建出既高性能又用户友好的 Vue 应用。