编译器仅将suspense标签转为suspenseimpl类型的vnode并拆分插槽,不解析异步逻辑;其占位与激活由运行时在vnode层动态控制,依据子组件异步状态自动切换fallback与default内容。

编译器本身不直接处理 Suspense 的行为,它只负责把模板语法转成标准的 render 函数和 VNode 描述。Suspense 的占位与激活逻辑发生在运行时的虚拟 DOM 渲染阶段,而非编译阶段。
编译器对 Suspense 的处理:仅生成结构化 VNode
当编译器遇到 <suspense></suspense> 标签时,会将其识别为一个内置组件,并生成对应类型的 VNode:
- type 字段设为
SuspenseImpl(内部实现类),而非普通元素或组件 - props 包含插槽信息,但不会提前解析 #default 或 #fallback 中的内容是否异步
- children 被拆分为两个独立的插槽函数:一个指向 #default 内容,另一个指向 #fallback 内容
- 整个过程不判断子组件是否异步,也不做任何加载控制——那是渲染器在 patch 阶段做的事
虚拟 DOM 的占位逻辑:VNode 层拦截,非 DOM 替换
Suspense 的“占位”不是靠 v-if 切换 DOM 节点,而是在 VNode 构建和 diff 过程中动态决定渲染哪一棵子树:
- 初始挂载时,若 #default 插槽内存在未 resolve 的异步边界(如 defineAsyncComponent、async setup),Vue 会跳过该子树的 VNode 创建
- 取而代之,生成一个特殊 shapeFlag 的 suspense 类型 VNode,其 children 直接引用 #fallback 插槽返回的 VNode 数组
- 真实 DOM 中此时只挂载 fallback 内容;浏览器看不到目标组件,也不存在“隐藏再显示”的闪烁
- 当异步操作完成,Vue 触发内部 resolve 流程,重新调用 #default 插槽函数生成新 VNode,并进入标准 patch 流程完成替换
激活时机:依赖异步边界完成,非手动触发
Suspense 的切换完全由 Vue 运行时自动检测驱动,关键触发条件包括:
- 子组件使用
defineAsyncComponent()动态导入 - 组件 setup() 是 async 函数,且内部有 await 表达式未完成
- #default 插槽中任意子组件处于 pending 状态(支持嵌套)
- 只要有一个子项未就绪,整个 Suspense 就保持 fallback 状态;全部 resolve 后才统一激活 default 内容
这个机制让开发者无需维护 loading 状态变量,也不用写 v-if/v-else 控制显隐,所有协调都在虚拟 DOM 层透明完成。









