从源码角度理解Vue父子组件生命周期函数执行顺序

1,438 阅读3分钟

背景

经常使用Vue进行日常需求开发,却不敢说对Vue十分精通,每次感觉自己对Vue已经足够熟悉了,但是往往很快就被现实狠狠给了一巴掌😭。

Vue生命周期的问题应该算是Vue面试题中比较经典的问题了,面试Vue相关开发岗位时几乎都会被问到。关于Vue实例本身的生命周期,Vue官网上有一个比较清晰的介绍,参考链接,方便查看这里贴出生命周期图片

单个Vue组件的生命周期是按照上图流程进行运转的,但是父子组件之间生命周期钩子函数运行流程又是什么样子的呢?

问题

父子组件由于存在逻辑上的关系,在生命周期钩子函数运行顺序中应该有一定的逻辑关系,本文就beforeCreate、created、beforeMount、mounted这四个生命周期钩子函数进行详细分析,从源码层面了解父子组件间生命周期钩子执行顺序之间存在的逻辑关系。

首先,来看段示例代码:

<!DOCTYPE html>
<html lang="en">
  <head> <meta charset="utf-8"> <title>组件事件流测试</title> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head>
  <body>
    <div id="el">这里是父组件{{ name }}<child></child></div>
    <div id="event" style="margin-top: 15px;"><div>这里是事件顺序:</div>
      <div v-for="(item, index) in events">{{ item }}</div>
    </div>
    <script>
    const events = new Vue({
      el: '#event', data: { events: [] },
    });
    const createHooks = (name) => ({
      beforeCreate() { events.events.push(`${name} beforeCreate`); },
      created() { events.events.push(`${name} created`); },
      beforeMount() { events.events.push(`${name} beforeMount`); },
      mounted() { events.events.push(`${name} mounted`); },
    });
    Vue.component('child', { ...createHooks('child'), template: '<div>这里是子组件<child2 /></div>' });
    Vue.component('child2', { ...createHooks('child2'), template: '<div>这里是子子组件</div>' });
    var vm = new Vue({
      el: '#el', ...createHooks('parent'), data() { return { name: 'parent' }; }
    });
    </script>
  </body>
</html>

上面这段代码逻辑很简单,但是却足以说明问题了,聪明的你能够立马说出网页上显示的事件的顺序吗?

示例运行结果

可以在chrome中打开这个html页面,可以看到结果应该是这个样子的:

parent beforeCreate
parent created
parent beforeMount
child beforeCreate
child created
child beforeMount
child2 beforeCreate
child2 created
child2 beforeMount
child2 mounted
child mounted
parent mounted

你是否有从原理层面理解了这一结果呢?

在源码面前没有秘密

为了彻底搞明白出现这一结果的原因,我选择从源码入手,如果单纯记忆父子组件钩子函数执行顺序之间的逻辑关系,对于理解Vue的整体结构上还是有所不足的。

分析Vue,当然就从Vue初始化的入口开始,抽丝剥茧,于是乎我画了下面这个流程图,帮助梳理和理解钩子函数的运行逻辑,以及为什么父子组件钩子函数为什么会是这个运行顺序。

组件初始化流程

经过九牛二虎之力终于画出了Vue组件初始化的流程图,图中省略了一些细节部分,如有不妥之处,敬请指教。

image.png

整个流程确实挺复杂的,其实关键点就一个:子组件的初始化流程是在父组件进行渲染的时候进行的,父组件的挂载事件(mounted)是在子组件mounted之后触发的,简化流程之后,我们可以参考下面这个更简洁的流程图,帮助理解。

钩子运行顺序

单纯从父子组件的生命周期来看,我们可以将上述流程图进行简化,重点关注父子组件生命周期函数的执行顺序。

flowchart TB
	subgraph parent
		beforeCreate-->created-->beforeMount
	end
	subgraph child
		childBeforeCreate[child beforeCreate]-->childCreated[child created]-->childBeforeMount[child beforeMount]
	end
	parent --> child --> childMounted[child mounted钩子]-->mounted[父组件mounted钩子]

后续

关于Vue中其他钩子函数:beforeUpdate、updated、beforeUnmount、unmounted等,也可以参考流程图的方式进行梳理。通过源码分析的方式梳理钩子函数的执行逻辑,是不是对上述代码的执行逻辑有了更近一步的认识?