先记住这个答案
普通同步父子组件首次挂载时,常见顺序是:父 setup、父 onBeforeMount、子 setup、子 onBeforeMount、子 onMounted、父 onMounted。可以理解为父级先开始创建并进入子树,子组件完成自身挂载后,父组件才满足已挂载条件。这个顺序需限定同步子树;异步组件、Suspense、条件分支和多个兄弟不能靠一张简化表推断所有完成时点。
- setup 初始化由父向子进入
- 同步子 mounted 先于父 mounted
- 异步子树需要自己的完成信号
向下初始化、向上完成解释顺序
父组件必须先执行 setup 才能得到渲染函数或模板所需状态,随后创建子组件。父级自己的 mounted 需要等待同步子树已插入,因此子级完成钩子先被调用,再回到父级。
onBeforeMount 的位置也要以组件自身为参照:父级准备挂载后才开始真正处理子树,子级的 setup 因而位于父 beforeMount 之后。背诵列表之外,理解递归关系更容易推导嵌套三层。
用共享事件数组观察同步子树
下面的父子组件分别记录 setup、beforeMount 与 mounted。挂载 Parent 后,事件数组精确展示父开始、进入子级、子完成、父完成的过程,不需要真实浏览器 DOM。
测试只能证明受控同步组件的钩子顺序。若 Child 改为异步组件,父 mounted 不承诺等待其解析;产品需要等待异步内容时,应由异步边界提供显式 ready 状态。
import { h, onBeforeMount, onMounted } from 'vue'
const events: string[] = []
const Child = {
setup() {
events.push('child setup')
onBeforeMount(() => events.push('child beforeMount'))
onMounted(() => events.push('child mounted'))
return () => h('span', 'child')
}
}
const Parent = {
setup() {
events.push('parent setup')
onBeforeMount(() => events.push('parent beforeMount'))
onMounted(() => events.push('parent mounted'))
return () => h(Child)
}
}挂载同步 Parent 后,记录依次为父 setup、父 beforeMount、子 setup、子 beforeMount、子 mounted、父 mounted。示例不声明异步子组件或浏览器绘制完成。
不要用顺序暗中耦合兄弟组件
如果兄弟乙必须等待兄弟甲在 mounted 中写入全局变量,代码依赖渲染遍历细节,条件渲染或重排后就可能改变。共享准备流程应上移到共同父级或显式服务,并通过状态与 Promise 表达完成。
父级需要使用子组件公开实例时,可以在 mounted 后访问同步子级模板引用,但仍要处理条件分支为空。需要子级数据就绪时则监听明确的 ready 或事件,不能把子 mounted 和异步业务完成混为一谈。
容易答错的地方
- 回答成父 mounted 先于子 mounted
- 父级的挂载完成需要同步子树完成,因此完成钩子从子级返回父级。父级 setup 确实先运行,但不能把初始化进入顺序直接套到完成钩子。
- 把父 mounted 当成所有后代都已完成
- 异步组件和 Suspense 分支不在同步保证内,图片、请求和动画更有自己的时序。需要哪一种完成状态,就使用对应信号并覆盖失败或卸载情况。
面试官还会怎么问?
三层组件 mounted 顺序如何推导?
setup 和 beforeMount 沿父、子、孙逐层进入;完成挂载后从最深同步后代逐层返回,因此孙 mounted、子 mounted、父 mounted。仍需限定同步且实际渲染的分支。
更新阶段也是子 updated 先于父吗?
当同一次更新同时影响父子时,父级先进入更新,子级先完成,通常子 updated 在父 updated 之前。具体问题应观察谁的状态触发了谁,不要把首次挂载表无条件套到所有更新。
卸载时应该在哪层释放共享资源?
资源由谁创建和拥有,就由对应层或共享服务释放。父级可在自己的卸载流程结束整个子树,但多个组件共享的外部资源应有独立 owner,避免某个子级先离开就误关其他消费者。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。