Suspense
本教程共 40 篇 · 第 34 篇 · 更新于 2026-07-29 · 约 6 分钟阅读
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>
工作流程:
- 初次渲染时,
<Suspense>在内存里渲染默认内容 - 发现异步依赖 → 进入 pending 状态 → 显示
#fallback - 所有异步依赖 resolve → 进入 resolved 状态 → 显示默认内容
状态切换规则
进入 resolved 状态后,只有默认插槽的根节点被替换时才会重新进入 pending。更深层的异步依赖变化不会触发。
重新进入 pending 时,不会立刻显示 fallback,而是继续显示旧内容。等新的异步依赖都 resolve 了再切换。
如果想超时切换,用 timeout 属性:
<Suspense :timeout="3000">
<!-- 3 秒后还没 resolve 就显示 fallback -->
</Suspense>
timeout="0" 表示根节点替换后立即显示 fallback。
事件
<Suspense> 触发三个事件:
| 事件 | 时机 |
|---|---|
pending | 进入 pending 状态 |
resolve | 新的默认内容 resolve 完成 |
fallback | fallback 内容显示 |
<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属性协调