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父子组件生命周期执行顺序及钩子函数

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值