Vue 2 与 Vue 3 的生命周期如何对照?父子组件钩子按什么顺序执行?
Vue 2 的 beforeDestroy/destroyed 在 Vue 3 中改名为 beforeUnmount/unmounted,组合式 API 用 onXxx 注册。父子组件创建时父先开始、子先挂载完成;更新和销毁同样是父先开始、子先结束。
参考答案
Vue 2 已于 2023-12-31 停止维护,本题 Vue 2 部分以 2.7 为准。何时请求数据、何时清理资源见《Vue 生命周期中何时访问 DOM、发请求和清理资源?》,本题只讲版本对照与执行顺序。
简答
- 选项式钩子在 Vue 3 中基本保留,只有两个改名:
beforeDestroy→beforeUnmount,destroyed→unmounted。 - 组合式 API 没有
beforeCreate/created对应函数,setup()本身承担这部分工作,并且在所有选项式钩子之前执行,包括beforeCreate。 - 创建阶段:父组件执行到
beforeMount后开始渲染子组件,子组件全部mounted后父组件才mounted。 - 更新阶段:父
beforeUpdate→ 子beforeUpdate→ 子updated→ 父updated。 - 销毁阶段(Vue 2):父
beforeDestroy→ 子beforeDestroy→ 子destroyed→ 父destroyed;Vue 3 的卸载顺序相同,只是名字换成 unmount。
钩子对照表
| Vue 2 选项 | Vue 3 选项 | Vue 3 组合式 API(2.7 也可用) |
|---|---|---|
beforeCreate | beforeCreate | 无,写在 setup() 中 |
created | created | 无,写在 setup() 中 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
updated | updated | onUpdated |
beforeDestroy | beforeUnmount | onBeforeUnmount |
destroyed | unmounted | onUnmounted |
activated / deactivated | 同名 | onActivated / onDeactivated |
errorCaptured | 同名 | onErrorCaptured |
改名记录在迁移指南的“Other Minor Changes”中。Vue 2.7 把组合式 API 移植回 Vue 2,所以 onMounted、onBeforeUnmount 等也能在 2.7 中使用,便于升级前先改写。
父子顺序为什么是“父先开始、子先结束”
父组件渲染时才会创建子组件。父组件进入 beforeMount 后执行渲染和 patch,遇到子组件就完整走一遍子组件的创建与挂载;子组件的 DOM 插入父组件的 DOM 树后,父组件才算挂载完成。
更新时,调度器按 watcher 创建顺序(父先于子)运行渲染 watcher,所以父组件先触发 beforeUpdate;updated 在整个队列执行完后统一调用,Vue 2 源码 callUpdatedHooks 倒序遍历队列,因此子组件的 updated 先于父组件。
销毁时,父组件先触发 beforeDestroy,再递归销毁子组件,最后自己触发 destroyed。
示例:实际运行结果
JavaScript
// vue@2.7.16,在浏览器中运行
const log = [];
const names = ['beforeCreate', 'created', 'beforeMount', 'mounted',
'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed'];
const hooks = (who) => Object.fromEntries(
names.map((h) => [h, () => log.push(`${who} ${h}`)]),
);
const Child = { props: ['msg'], template: '<i>{{ msg }}</i>', ...hooks('子') };
const vm = new Vue({
data: { msg: 'a' },
components: { Child },
template: '<div><Child :msg="msg" /></div>',
...hooks('父'),
}).$mount();
vm.msg = 'b'; // 触发更新
Vue.nextTick(() => vm.$destroy());
输出按阶段整理如下:
text
创建:父 beforeCreate, 父 created, 父 beforeMount,
子 beforeCreate, 子 created, 子 beforeMount, 子 mounted, 父 mounted
更新:父 beforeUpdate, 子 beforeUpdate, 子 updated, 父 updated
销毁:父 beforeDestroy, 子 beforeDestroy, 子 destroyed, 父 destroyed
把同一段代码换成 Vue 3.5(beforeUnmount/unmounted,app.unmount())运行,三个阶段的顺序完全一致。
常见追问
- 子组件的
props变化一定会触发父组件的beforeUpdate吗?不一定。上例中父组件模板读取了msg,所以父组件也会更新;如果只有子组件依赖的数据变了,只有子组件更新。 - 在父组件
mounted中能访问子组件的 DOM 吗?能,子组件此时已挂载。但异步组件或v-if后来才渲染的子组件不在此列。 - Vue 3 选项式 API 还能写
beforeDestroy吗?不能直接使用,需要改名;迁移构建@vue/compat会对旧名给出兼容与警告。