首页 / Vue3 入门教程 / Suspense

Vue3 入门教程

Suspense

本教程共 40 篇 · 第 34 篇 · 更新于 2026-07-29 · 约 6 分钟阅读

VueVue3 入门教程Suspense异步组件加载状态async setup

34. Suspense

本节目标:学会用 <Suspense> 统一管理多个异步组件的加载状态,告别满屏 loading 的尴尬。

问题场景

想象一个页面有多个区域,每个区域都要异步加载数据:

<Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus> (异步组件)
   └─ <Content>
      ├─ <ActivityFeed> (异步组件)
      └─ <Stats> (异步组件)

没有 <Suspense> 的话,每个组件自己处理 loading,页面上可能出现三个转圈,内容一块一块蹦出来。

<Suspense> 让你能在顶层统一控制加载状态,等所有异步依赖都准备好了再显示内容。

Warning

<Suspense> 目前还是实验性特性,API 未来可能调整。

两种异步依赖

<Suspense> 能等待两种异步操作:

1. async setup()

组件的 setup 函数可以是异步的:

<script setup>
const res = await fetch('/api/posts')
const posts = await res.json()
</script>

<template>
  {{ posts }}
</template>

<script setup> 里出现顶层 await,组件就自动变成异步依赖。

2. 异步组件

defineAsyncComponent 定义的组件:

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

异步组件默认是”suspensible”的——如果父链上有 <Suspense>,就由 <Suspense> 控制加载状态。

如果想让异步组件自己管理加载,设置 suspensible: false

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

基本用法

<Suspense> 有两个插槽:#default#fallback

<Suspense>
  <!-- 默认内容:有异步依赖 -->
  <Dashboard />

  <!-- 加载状态 -->
  <template #fallback>
    <div>加载中...</div>
  </template>
</Suspense>

工作流程:

  1. 初次渲染时,<Suspense> 在内存里渲染默认内容
  2. 发现异步依赖 → 进入 pending 状态 → 显示 #fallback
  3. 所有异步依赖 resolve → 进入 resolved 状态 → 显示默认内容

状态切换规则

进入 resolved 状态后,只有默认插槽的根节点被替换时才会重新进入 pending。更深层的异步依赖变化不会触发。

重新进入 pending 时,不会立刻显示 fallback,而是继续显示旧内容。等新的异步依赖都 resolve 了再切换。

如果想超时切换,用 timeout 属性:

<Suspense :timeout="3000">
  <!-- 3 秒后还没 resolve 就显示 fallback -->
</Suspense>

timeout="0" 表示根节点替换后立即显示 fallback。

事件

<Suspense> 触发三个事件:

事件时机
pending进入 pending 状态
resolve新的默认内容 resolve 完成
fallbackfallback 内容显示
<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">

错误处理

<Suspense> 本身不提供错误处理。你需要在父组件用 onErrorCaptured 钩子捕获:

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

onErrorCaptured((err) => {
  console.error('异步错误:', err)
  return false // 阻止错误继续向上传播
})
</script>

与其他组件配合

<Suspense> 经常和 <Transition><KeepAlive><RouterView> 一起用。嵌套顺序很重要:

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <component :is="Component" />
          <template #fallback>加载中...</template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

效果:路由切换有过渡动画,组件被缓存保留状态,异步依赖统一管理加载。

嵌套 Suspense(Vue 3.3+)

多个异步组件嵌套时,内层的异步变化不会触发外层 <Suspense> 的 pending。可以用嵌套 <Suspense> 解决:

<Suspense>
  <component :is="DynamicAsyncOuter">
    <Suspense suspensible>
      <component :is="DynamicAsyncInner" />
    </Suspense>
  </component>
</Suspense>
Note

内层 <Suspense>suspensible 属性后,异步依赖的处理权交给外层。不加的话内层会独立管理自己的 fallback,可能导致内容闪烁。

本节回顾

  • <Suspense> 统一管理多个异步依赖的加载状态
  • 支持 async setup() 和异步组件两种异步依赖
  • #default 放实际内容,#fallback 放加载状态
  • timeout 控制超时切换
  • 错误处理用 onErrorCaptured
  • 嵌套顺序:Transition > KeepAlive > Suspense
  • 嵌套 Suspense 用 suspensible 属性协调