[vuejs 系列] 父子组件的生命周期顺序刨根问底

Posted 大前端视野

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[vuejs 系列] 父子组件的生命周期顺序刨根问底相关的知识,希望对你有一定的参考价值。

本文为父子组件生命周期的开篇:

在常见的单页应用中,我们都会有一个根 App.vue 文件,里面放置一个 router-view 然后配置路由来切换.

很多人在子父组件嵌套关系下的生命周期钩子函数如何应用,谁先谁后(比如哪个用来发送请求,数据传递)等有所疑问。

本文聚焦 mounted 事件(需要 created 的可以留言哈),先抛结论:

子组件一层一层往 触发,最终触发根 App.vue 的 mounted

验证的做法很简单:

只需要在每一个嵌套组件里面的 mounted 增加打印日志就可以看到了
我们具体来看看内部的设计原理

现在假设我们配置了路由(片段来自 vue-router 的常见嵌套配置):

一级是 /user/:id 二级是 profile

const router = new VueRouter({  routes: [    {      path: '/user/:id',      component: User,      children: [        {          // 当 /user/:id/profile 匹配成功,          // UserProfile 会被渲染在 User 的 <router-view> 中          path: 'profile',          component: UserProfile        }      ]    }  ] })

先看一下所有的 mounted 最终在各自组件里面是如何被调用的:

通过 vm.$options 获取组件内部的配置,然后通过 call 方法

下面的我们又会遇到 vnode(所以掌握它很重要,在前面的  也提到了一些 vnode,感兴趣可以看看),就是下面 componentVNodeHooks 里面的 insert 函数的参数

var componentVNodeHooks = {  insert: function insert (vnode) {} }

里面呢,第一步:从 vnode 里面获取 componentInstance

var componentInstance = vnode.componentInstance;

然后判断 _isMounted 是否已经执行过 mounted (很常用的状态二次确定的变量,前面的 _ 一般代表内部使用)

if (!componentInstance._isMounted) {  componentInstance._isMounted = true;  callHook(componentInstance, 'mounted'); }

上面我们就用到了 callHook 函数了,传入的第二个参数也正是本文讨论的生命周期的 mounted

再往后有一个 invokeInsertHook 函数

function invokeInsertHook (vnode, queue, initial) { }

注意一下源码里面的注释:

delay insert hooks for component root nodes, invoke them after the element is really inserted

设置了 pendingInsert(后面会在 initComponent 中使用),代码如下:

if (isTrue(initial) && isDef(vnode.parent)) {  vnode.parent.data.pendingInsert = queue; }

内部设计:循环 queue 这个包含 vnode 的数组对象,如图所示:

注意一下标注的 data.hook,下面的代码片段会使用到,也就是调用上面提到的 componentVNodeHooks 对象的 insert

for (var i = 0; i < queue.length; ++i) {  queue[i].data.hook.insert(queue[i]); }

再往下,带着疑问:

这个 queue 是如何生成 vnode 数组的呢?

最开始定义一个空数组:

var insertedVnodeQueue = [];

在刚才的 对象中还有 init

var componentVNodeHooks = {  init: function init () {    
    // ...  } }

init 函数内部定义一个 child

var child = vnode.componentInstance = createComponentInstanceForVnode(...)

然后会调用一个 $mount

child.$mount(hydrating ? vnode.elm : undefined, hydrating);

在函数 initComponent 中会用到之前的 pendingInsert,而且 insertedVnodeQueue 这个数组对象会调用 push 插入元素

function initComponent (vnode, insertedVnodeQueue) {  if (isDef(vnode.data.pendingInsert)) {    
   insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);    vnode.data.pendingInsert = null;  } }

在函数 invokeCreateHooks 内部insertedVnodeQueue 这个数组对象会调用 push 插入元素

function invokeCreateHooks (vnode, insertedVnodeQueue) {  i = vnode.data.hook; // Reuse variable  if (isDef(i)) {    
   if (isDef(i.insert)) {      insertedVnodeQueue.push(vnode);    }  } }

在函数 mountComponent 内部当 vm.$vnode 为 null 也会调用 callHook,第二个参数传入 mounted

function mountComponent () {  
 if (vm.$vnode == null) {    vm._isMounted = true;    callHook(vm, 'mounted');  } }


以上是关于[vuejs 系列] 父子组件的生命周期顺序刨根问底的主要内容,如果未能解决你的问题,请参考以下文章

VUE生命周期中的钩子函数及父子组件的执行顺序

Vue中父子组件生命周期的执行顺序

Vue父子组件生命周期执行顺序

vue 父子组件生命周期函数执行顺序

vue父子组件加载顺序

前端技能树,面试复习第 46 天—— Vue 生命周期 | 父子组件钩子的执行顺序 | 组件间通信有哪些方式