先记住这个答案
父组件 onMounted 被调用时,自己的节点树与所有同步子组件已经挂载;这个保证不包括异步组件或 Suspense 树中的组件。异步加载器尚未解析时,父级可以先 mounted,页面只保留加载中的注释节点或后备内容。父级若依赖异步子级的业务就绪,应使用 Suspense 事件、子组件显式 emit/ready 状态或共同数据流程,而不是根据父 mounted 或固定延迟猜测。还要覆盖加载失败、超时和父级提前卸载。
- 同步子组件包含在父 mounted 保证内
- 异步组件与 Suspense 子树不在该保证内
- 业务就绪需要显式完成信号
异步边界允许父级先完成
defineAsyncComponent 的加载器返回 Promise,Vue 可以先把父级其余内容挂载起来。若父 mounted 必须等加载器才运行,异步页面的外壳和后备体验就无法独立建立。
Suspense 提供另一种协调异步依赖的边界,但也不该把任意网络请求都隐式当成组件 mounted。需要明确哪些依赖由边界等待,哪些是组件挂载后持续加载的数据。
用受控 Promise 证明先后关系
下例由调用方传入异步组件加载器,父级记录 mounted,异步子级解析并挂载时再记录 child mounted。测试可以先挂父级、断言只有 parent,再主动解析加载器。
这种可控加载比依赖真实网络延迟稳定,也能覆盖父级先卸载的情况。实际组件应配置加载、错误与重试体验,不能让 Promise 永久 pending 后只留下空白区域。
import { defineAsyncComponent, h, onMounted } from 'vue'
function makeParent(load: () => Promise<object>, events: string[]) {
const AsyncChild = defineAsyncComponent(load)
return {
setup() {
onMounted(() => events.push('parent mounted'))
return () => h('section', [h(AsyncChild)])
}
}
}
const Child = {
setup() {
onMounted(() => asyncEvents.push('child mounted'))
return () => h('span', 'child')
}
}
const asyncEvents: string[] = []用受控 Promise 作为 load,挂载父组件后 asyncEvents 先只有 parent mounted;解析为 Child 并等待刷新后才追加 child mounted。示例限定单个异步后代,不涉及 Suspense 后备切换。
用显式 ready 表达业务依赖
父级若要在子图表初始化后调整布局,让子组件在真实初始化成功时 emit ready,比把逻辑塞进父 mounted 更准确。事件载荷可包含测量结果或实例标识,并在子组件重建时重新发出。
如果父级等待期间已经卸载,迟到事件或 Promise 结果不应再写入旧状态。取消可取消任务,或者用实例标识和有效标记拒绝旧结果,避免异步完成后访问已经不存在的引用。
容易答错的地方
- 把父 mounted 当成递归等待所有任务
- 它覆盖同步子组件挂载,不会等待异步模块、普通请求、图片或动画。回答时把保证限定到组件生命周期层,另外说明业务资源各有完成信号。
- 用 setTimeout 推测异步子组件已完成
- 固定时长无法覆盖缓存命中、慢网、错误和卸载。应观察加载 Promise、Suspense 边界或子组件自己的 ready,测试也应由受控 Promise 决定完成顺序。
面试官还会怎么问?
同步子组件的 mounted 与父 mounted 谁先?
同步子组件先完成 mounted,父级随后 mounted。初始化则由父 setup 先进入子树,因此要区分向下创建与向上完成两个方向。
异步组件加载失败后父 mounted 会撤销吗?
不会因为后代之后加载失败而撤销已经发生的父级挂载。错误应由异步组件配置、错误边界或应用错误处理接住,父级根据状态展示可恢复界面。
父级如何等待多个异步子组件都 ready?
为每个实际业务依赖定义稳定标识和完成状态,在父级或协调层聚合;还要处理组件被移除、失败和重新加载。只统计 mounted 次数容易把旧实例的迟到事件算入当前集合。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。