本篇文章带大家了解一下vue中的生命周期钩子,介绍一下vue中的mounted钩子,希望对大家有所帮助!

注:阅读本文需要对vue的patch流程有较清晰的理解,如果不清楚patch流程,建议先了解清楚这个流程再阅读本文,否则可能会感觉云里雾里。
聊之前我们先看一个场景
<div id="app"> <aC /> <bC /></div>
如上所示,App.vue文件里面有两个子组件,互为兄弟关系
组件里面自各有created和mounted两个生命周期钩子,a表示组件名 C是created的缩写
// a组件created() { console.log('aC') },mounted() { debugger console.log('aM')}, // b组件created() { console.log('bC') },mounted() { debugger console.log('bM')},请问打印顺序是什么?各位读者可以先脑补一下,后面看看对不对。
如果对vue patch流程比较熟悉的读者,可能会认为顺序是aC→aM→bC→BM,也就是a组件先创建,再挂载,然后到b组件重复以上流程。因为从patch的方法里面可以知道,组件created后,再走到insert进父容器的过程,是一个同步的流程,只有这个流程走完后,才会遍历到b组件,走b组件的渲染流程。
实际上浏览器打印出来的顺序是aC→bC→aM→bM,也就是两个created先执行,才到mounted执行,和上面的分析相悖。这里先说原因,子组件从created到insert进父容器的过程还是同步的,但是insert进父容器后,也可以理解为子组件mounted,并没有马上调用mounted生命周期钩子。下面从源码角度分析一下:
先大概回顾一下子组件渲染流程,patch函数调用createElm创建真实element,createElm里面通过createComponent判断当前vnode是否组件vnode,是则进入组件渲染流程
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; if (isDef(i)) { var isReactivated = isDef(vnode.componentInstance) && i.keepAlive; if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */); } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue); // 最终组件创建完后会走到这里 把组件对应的el插入到父节点 insert(parentElm, vnode.elm, refElm); if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true } }}createComponent里面就把组件对应的el插入到父节点,最后会返回到patch调用栈,调用
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
因为子组件有vnode.parent所以会走一个分支,但是我们也看看第二个分支调用的insert是什么
function invokeInsertHook (vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; } else { for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); } }}这个insert是挂在vnode.data.hook上,在组件创建过程中,createComponent方法里面有一个调用
installComponentHooks,在这里把insert钩子注入了。这个方法实际定义在componentVNodeHooks对象里面,可以看到这个insert里面调用了callHook(componentInstance, 'mounted'),这里实际上就是调用子组件的mounted生命周期。
insert: function insert (vnode) { var context = vnode.context; var componentInstance = vnode.componentInstance; if (!componentInstance._isMounted) { componentInstance._isMounted = true; callHook(componentInstance, 'mounted'); } if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. queueActivatedComponent(componentInstance); } else { activateChildComponent(componentInstance, true /* direct */); } }},再来看看这个方法,子组件走第一个分支,仅仅执行了一行代码vnode.parent.data.pendingInsert = queue , 这个queue实际是在patch 开始时候,定义的insertedVnodeQueue。这里的逻辑就是把当前的insertedVnodeQueue,挂在parent的vnode data的pendingInsert上。
function invokeInsertHook (vnode, queue, initial) { // delay insert hooks for component root nodes, invoke them after the // element is really inserted if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue; } else { for (var i = 0; i < queue.length; ++i) { queue[i].data.hook.insert(queue[i]); } }}// 在patch 开始时候 定义了insertedVnodeQueue为一个空数组var insertedVnodeQueue = [];源码里面再搜索insertedVnodeQueue ,可以看到有这样一段逻辑,initComponent还是在createComponent里面调用的
function initComponent (vnode, insertedVnodeQueue) { if (isDef(vnode.data.pendingInsert)) { insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); vnode.data.pendingInsert = null; } vnode.elm = vnode.componentInstance.$el; if (isPatchable(vnode)) { // ??注意这个方法 invokeCreateHooks(vnode, insertedVnodeQueue); setScope(vnode); } else&n

