Vue中父子组件的生命周期

本文详细解析了Vue中父子组件的生命周期执行顺序,包括渲染、更新和销毁过程,并通过实例演示了不同阶段的状态。

今天偶然看到一个问题,是关于vue父子组件生命周期的,自己最后也没有写出正确的答案,所以特此记录一下这个知识点。(以下均为基于vue2.x的情况,vue3.x生命周期有所变化,但是同样可以类比)

问题

首先给出我遇到的问题,这也是平常笔试面试可能遇到的问题:
假设以下生命周期中含有下列功能

  • 父组件beforeCreate:打印a1
  • 父组件created:打印a2
  • 父组件beforeMount:打印a3
  • 父组件mounted:打印a4
  • 子组件beforeCreate:打印b1
  • 子组件created:打印b2
  • 子组件beforeMount:打印b3
  • 子组件mounted:打印b4

那在执行过程中,控制台打印的结果是什么?

结果

这个问题考点其实就是父组件和子组件在执行时的生命周期调用顺序,我们可以思考之后给出自己的答案,然后在代码上加以验证看看是否正确,这里直接给出答案:a1 a2 a3 b1 b2 b3 b4 a4。你的答案正确吗?

思考

从上面的答案不难反推出,当父子组件在构建渲染时,生命周期存在这样的关系:
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
接下来就来探究一下为什么会是这样的顺序

生命周期

首先了解一下生命周期,上一张官方生命周期图
在这里插入图片描述
先不用去考虑上图中具体的细节,只要了解图中的流程就可以理解今天这个问题。根据官方文档,vue生命周期钩子函数中要完成的事件可以整理为下表:

生命周期钩子函数事件以及处理
beforeCreate在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
created实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用。
mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
beforeUpdate数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
activatedkeep-alive 组件激活时调用。
deactivatedkeep-alive 组件停用时调用。
beforeDestroy实例销毁之前调用。在这一步,实例仍然完全可用。
destroyedVue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁

上面的介绍,多少有点抽象,接下来就结合实例来进一步分析这个过程

实例测试

这里给出一段调试代码:

 var vm = new Vue({
    el: '#app',
    data: {
      message: 'Vue的生命周期'
    },
    beforeCreate: function() {
      console.group('------beforeCreate创建前状态------');
      console.log("%c%s", "color:red" , "el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //undefined 
      console.log("%c%s", "color:red","message: " + this.message)
    },
    created: function() {
      console.group('------created创建完毕状态------');
      console.log("%c%s", "color:red","el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化 
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化
    },
    beforeMount: function() {
      console.group('------beforeMount挂载前状态------');
      console.log("%c%s", "color:red","el     : " + (this.$el)); //已被初始化
      console.log(this.$el);
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化  
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化  
    },
    mounted: function() {
      console.group('------mounted 挂载结束状态------');
      console.log("%c%s", "color:red","el     : " + this.$el); //已被初始化
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化 
    },
    beforeUpdate: function () {
      console.group('beforeUpdate 更新前状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);   
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    updated: function () {
      console.group('updated 更新完成状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el); 
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    beforeDestroy: function () {
      console.group('beforeDestroy 销毁前状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    destroyed: function () {
      console.group('destroyed 销毁完成状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);  
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message)
    }
  })

运行后控制台得到的结果为:
在这里插入图片描述
从以上实例可以看出,在组件的beforeCreate阶段,并不能监测到数据,data为undefined,$el也是undefined,无可操作数据。实际上这个时候只进行了一些必要的初始化操作,比如全局配置、属性初始化等

在created阶段,组件实例已经完成了数据代理和数据的绑定,data已经有值(这个时候其实methods中的方法也已经完成加载,已经watch/event回调),但是$el属性依然是不可见的。
在beforeMount阶段,结合生命周期图,我们可以知道调用这个钩子函数前会首先执行模板解析函数,这个阶段$el属性已经可以打印出值,但是从打印结果可以看出(如下)
在这里插入图片描述
el属性中的message并未被替换成动态数据。其实这里使用的就是虚拟Dom,先把坑占下了,等待元素真正挂载到页面时再把值渲染进去。

在mounted阶段,可以看到值已经被渲染,el也被挂载到了实例上(如图)
在这里插入图片描述

结论

所以结合上面这个实例,可以得出,当父组件进行到beforeMount阶段,即开始模板解析时,就发现需要去构建子组件,所以这个时候会触发子组件的生命周期,当子组件执行到mounted阶段时,子组件已经挂载到了父组件上,父组件随之进入到自身的mounted阶段,将自身挂载。

延伸

除了渲染过程,还有父子组件更新、销毁等过程,都可以按照上面的方式来自己推敲(实例中也写了相关的生命周期钩子),这里直接给出结论

  • 子组件更新:父beforeUpdate->子beforeUpdate->子updated->父updated
  • 父组件更新:父beforeUpdate->父updated
  • 父组件销毁:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

参考资料

父子组件生命周期执行
深入理解Vue父子组件生命周期执行顺序及钩子函数

<think>嗯,用户的问题是关于Vue中多层级组件嵌套时,各个组件的beforeMount和mounted钩子的执行顺序。首先,我需要回忆一下Vue生命周期相关知识。根据之前的引用内容,生命周期钩子如beforeMount和mounted在实例的不同阶段被调用,且this指向Vue实例[^1][^4]。 在多层组件嵌套的情况下,比如父组件包含子组件,子组件又包含孙组件,它们的生命周期钩子执行顺序应该是什么样的呢?我记得Vue的组件渲染是自上而下解析,但挂载是从内到外还是从外到内呢?可能需要考虑父子组件的创建和挂载过程。 根据Vue生命周期流程,父组件在创建过程中,会先执行自己的beforeCreate、created、beforeMount钩子,然后开始渲染子组件。子组件同样会经历自己的生命周期钩子,执行完子组件的mounted之后,父组件的mounted才会被触发。这是因为父组件需要等待所有子组件都挂载完成后才会完成自己的挂载。 例如,假设结构是Root -> Parent -> Child。执行顺序应该是Root的beforeMount,Parent的beforeMount,Child的beforeMount,然后是Child的mounted,接着Parent的mounted,最后Root的mounted。这样的话,beforeMount是从父到子,而mounted是从子到父。 需要确认是否正确。可能查阅官方文档或相关资料。根据引用中的内容,Vue组件触发动作到Vuex的流程,但这里主要关注生命周期钩子的执行顺序。另外,引用4提到钩子函数的执行时间不会被延时器影响,所以同步执行的情况下,顺序是确定的。 因此,可以得出结论:beforeMount钩子的执行顺序是父组件先于子组件,而mounted则是子组件先于父组件。这种顺序是因为父组件需要先准备好自己的模板结构(包括子组件的占位符),然后子组件实例化并挂载,完成后父组件才能完成挂载。</think>### Vue多层级组件生命周期钩子执行顺序分析 在Vue中,**多层级嵌套组件的生命周期钩子执行顺序遵循“父组件初始化→子组件挂载→父组件完成挂载”的规律**,具体表现为: --- #### 1. **组件嵌套结构示例** 假设组件层级为: ``` Root → Parent → Child ``` #### 2. **生命周期钩子执行顺序** 按照Vue的渲染机制,执行流程如下: 1. **Root组件**:触发`beforeCreate` → `created` → `beforeMount` 2. **Parent组件**:触发`beforeCreate` → `created` → `beforeMount` 3. **Child组件**:触发`beforeCreate` → `created` → `beforeMount` 4. **Child组件**:触发`mounted` 5. **Parent组件**:触发`mounted` 6. **Root组件**:触发`mounted` 即: $$ \text{beforeMount顺序:Root→Parent→Child} \\ \text{mounted顺序:Child→Parent→Root} $$ --- #### 3. **原理说明** - **初始化阶段**:父组件的模板需要先解析到子组件占位符,才会触发子组件的初始化。因此`beforeMount`按父→子顺序执行[^4]。 - **挂载阶段**:子组件必须完成DOM挂载后,父组件才能确认自身完全挂载。因此`mounted`按子→父顺序执行[^4]。 --- #### 4. **实验验证** 通过以下代码可验证顺序: ```javascript // Child组件 export default { beforeMount() { console.log('Child beforeMount') }, mounted() { console.log('Child mounted') } } // Parent组件 import Child from './Child.vue'; export default { components: { Child }, beforeMount() { console.log('Parent beforeMount') }, mounted() { console.log('Parent mounted') } } // Root组件 import Parent from './Parent.vue'; export default { components: { Parent }, beforeMount() { console.log('Root beforeMount') }, mounted() { console.log('Root mounted') } } ``` 输出结果: ``` Root beforeMount Parent beforeMount Child beforeMount Child mounted Parent mounted Root mounted ``` --- #### 5. **扩展说明** - **异步组件**:若子组件是异步加载的,其生命周期会延迟到资源加载完成后触发[^4]。 - **Vue3差异**:Vue3的组合式API中,`setup`函数替代了`beforeCreate`和`created`,但挂载阶段的顺序逻辑保持一致。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值