- 13.7 生命周期
- 13.7.1 deactivated
- 13.7.2 activated
13.7 生命周期
我们通过例子来观察keep-alive生命周期和普通组件的不同。

在我们从child1切换到child2,再切回child1过程中,chil1不会再执行mounted钩子,只会执行activated钩子,而child2也不会执行destoryed钩子,只会执行deactivated钩子,这是为什么?child2的deactivated钩子又要比child1的activated提前执行,这又是为什么?
13.7.1 deactivated
我们先从组件的销毁开始说起,当child1切换到child2时,child1会执行deactivated钩子而不是destoryed钩子,这是为什么?前面分析patch过程会对新旧节点的改变进行对比,从而尽可能范围小的去操作真实节点,当完成diff算法并对节点操作完毕后,接下来还有一个重要的步骤是对旧的组件执行销毁移除操作。这一步的代码如下:
function patch(···) {// 分析过的patchVnode过程// 销毁旧节点if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0);} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);}}function removeVnodes (parentElm, vnodes, startIdx, endIdx) {// startIdx,endIdx都为0for (; startIdx <= endIdx; ++startIdx) {// ch 会拿到需要销毁的组件var ch = vnodes[startIdx];if (isDef(ch)) {if (isDef(ch.tag)) {// 真实节点的移除操作removeAndInvokeRemoveHook(ch);invokeDestroyHook(ch);} else { // Text noderemoveNode(ch.elm);}}}}
removeAndInvokeRemoveHook会对旧的节点进行移除操作,其中关键的一步是会将真实节点从父元素中删除,有兴趣可以自行查看这部分逻辑。invokeDestroyHook是执行销毁组件钩子的核心。如果该组件下存在子组件,会递归去调用invokeDestroyHook执行销毁操作。销毁过程会执行组件内部的destory钩子。
function invokeDestroyHook (vnode) {var i, j;var data = vnode.data;if (isDef(data)) {if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }// 执行组件内部destroy钩子for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); }}// 如果组件存在子组件,则遍历子组件去递归调用invokeDestoryHook执行钩子if (isDef(i = vnode.children)) {for (j = 0; j < vnode.children.length; ++j) {invokeDestroyHook(vnode.children[j]);}}}
组件内部钩子前面已经介绍了init和prepatch钩子,而destroy钩子的逻辑更加简单。
var componentVNodeHooks = {destroy: function destroy (vnode) {// 组件实例var componentInstance = vnode.componentInstance;// 如果实例还未被销毁if (!componentInstance._isDestroyed) {// 不是keep-alive组件则执行销毁操作if (!vnode.data.keepAlive) {componentInstance.$destroy();} else {// 如果是已经缓存的组件deactivateChildComponent(componentInstance, true /* direct */);}}}}
当组件是keep-alive缓存过的组件,即已经用keepAlive标记过,则不会执行实例的销毁,即componentInstance.$destroy()的过程。$destroy过程会做一系列的组件销毁操作,其中的beforeDestroy,destoryed钩子也是在$destory过程中调用,而deactivateChildComponent的处理过程却完全不同。
function deactivateChildComponent (vm, direct) {if (direct) {//vm._directInactive = true;if (isInInactiveTree(vm)) {return}}if (!vm._inactive) {// 已经被停用vm._inactive = true;// 对子组件同样会执行停用处理for (var i = 0; i < vm.$children.length; i++) {deactivateChildComponent(vm.$children[i]);}// 最终调用deactivated钩子callHook(vm, 'deactivated');}}
_directInactive是用来标记这个被打上停用标签的组件是否是最顶层的组件。而_inactive是停用的标志,同样的子组件也需要递归去调用deactivateChildComponent,打上停用的标记。最终会执行用户定义的deactivated钩子。
13.7.2 activated
现在回过头看看activated的执行时机,同样是patch过程,在对旧节点移除并执行销毁或者停用的钩子后,对新节点也会执行相应的钩子。这也是停用的钩子比启用的钩子先执行的原因。
function patch(···) {// patchVnode过程// 销毁旧节点{if (isDef(parentElm)) {removeVnodes(parentElm, [oldVnode], 0, 0);} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);}}// 执行组件内部的insert钩子invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);}function invokeInsertHook (vnode, queue, initial) {// delay insert hooks for component root nodes, invoke them after the// 当节点已经被插入时,会延迟执行insert钩子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钩子逻辑如下:
// 组件内部自带钩子var componentVNodeHooks = {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 */);}}},}
当第一次实例化组件时,由于实例的_isMounted不存在,所以会调用mounted钩子,当我们从child2再次切回child1时,由于child1只是被停用而没有被销毁,所以不会再调用mounted钩子,此时会执行activateChildComponent函数对组件的状态进行处理。有了分析deactivateChildComponent的基础,activateChildComponent的逻辑也很好理解,同样的_inactive标记为已启用,并且对子组件递归调用activateChildComponent做状态处理。
function activateChildComponent (vm, direct) {if (direct) {vm._directInactive = false;if (isInInactiveTree(vm)) {return}} else if (vm._directInactive) {return}if (vm._inactive || vm._inactive === null) {vm._inactive = false;for (var i = 0; i < vm.$children.length; i++) {activateChildComponent(vm.$children[i]);}callHook(vm, 'activated');}}
