vue内置组件keep-alive生命周期解读

5,598 阅读2分钟

生命周期

组件一旦被 <keep-alive> 缓存,那么再次渲染的时候就不会执行 created、mounted 等钩子函数,但是我们很多实际业务场景都是希望在我们被缓存的组件再次被渲染的时候做一些事情。
所以Vue 针对被keep-alive包裹的组件提供了 activated和deactivated钩子函数。

执行顺序

页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated

源码解读

接下来我们从源码角度来分析一下它的实现原理。 直接到关键步骤》》》》》
在渲染的最后一步,会执行 invokeInsertHook,函数执行 vnode 的 insert 钩子函数,源码文件目录为src/core/vdom/create-component.js

const componentVNodeHooks = {
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    // 暂时不考虑为什么componentInstance._isMounted一定是true
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    // 在上篇中<keep-alive>组件render函数最后vnode.data.keepAlive = true,  
       那么必会进入该判断中
    if (vnode.data.keepAlive) {
      // 这里要先搞清楚vnode.context是啥,通过VNode类可知
          export default class VNode {
            tag: string | void;
            data: VNodeData | void;
            children: ?Array<VNode\>;
            text: string | void;
            elm: Node | void;
            ns: string | void;
            context: Component | void; // rendered in this component's scope
            // context指的是渲染这个dom的上下文对象,既当前vue实例
            key: string | number | void;
            componentOptions: VNodeComponentOptions | void;
            componentInstance: Component | void; // component instance
            parent: VNode | void; // component placeholder node;
            ...
          }
      // 暂时不考虑context._isMounted是true的情况
      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 */)
      }
    }
  },
  // ...
}

我们先分析context._isMounted为true的情况,那么会执行那么会执行activateChildComponent方法,activateChildComponent文件目录为src/core/instance/lifecycle.js,代码如下:

   export function activateChildComponent (vm: Component, direct?: boolean) {
      if (direct) {
        vm._directInactive = false
        // 辅助判断逻辑先不考虑
        if (isInInactiveTree(vm)) {
          return
        }
      } else if (vm._directInactive) {
        return
      }
      // _inactive表示keep-alive中组件状态,如被激活,该值为false,反之为true。
      if (vm._inactive || vm._inactive === null) {
        vm._inactive = false
        // 最终会执行activated钩子函数,并且递归去执行它的所有子组件的 activated 钩子函数。
        for (let i = 0; i < vm.$children.length; i++) {
          activateChildComponent(vm.$children[i])
        }
        callHook(vm, 'activated')
      }
  }

再回过头看queueActivatedComponent函数,文件目录为src/core/observer/scheduler.js,代码如下:

export function queueActivatedComponent (vm: Component) {
  vm._inactive = false
  activatedChildren.push(vm)
  // 把当前 vm 实例添加到 activatedChildren 数组中
}

那么activatedChildren数组的作用是什么呢?大胆猜测和更新队列相关 同样对应的deactivated钩子函数,它是发生在vnode的destory钩子函数,文件目录为src/core/vdom/create-component.js,代码如下:

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, 'deactivated')
  }

和activateChildComponent方法类似,就是执行组件的deacitvated钩子函数,
并且递归去执行它的所有子组件的deactivated钩子函数。

遗留问题

  • 为什么当keep-alive组件再次渲染是componentInstance._isMounted,context._isMounted一定是true;
  • activatedChildren数组的作用是什么

后续更新