Vue源码分析之keep-alive(三)

178 阅读2分钟

这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

前言

官方文档提到当缓存组件在<keep-alive>内被切换时,它的created和mounted生命周期不会被调用,前边的文章也看了为什么被keep-alive缓存过的组件不会去调用created,mounted等钩子函数。

但是我们日常的业务场景都是需要缓存组件再次被渲染的时候重新去请求接口更新数据等,没有mounted这些钩子函数,那我们怎么去处理呢??

官方为我们提供了另外两个钩子:activated和deactivated

  • activated:被keep-alive缓存的组件激活时调用(服务端渲染不调用)

  • deactivated:被keep-alive缓存的组件失活时调用(服务端渲染不调用)

这篇文就来看下这两个钩子的触发过程:

activated钩子

其实patch函数在执行完新老节点对比更新后,在最后一步执行了一个invokeInsertHook函数。

image.png

看这个函数的命名就知道与钩子有关,看一下具体做了啥:

invokeInsertHook

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 (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}

invokeInsertHook函数内部调用了组件Vnode的insert钩子,insert函数定义在src/core/vdom/create-component.js中

insert

insert (vnode: MountedComponentVNode) {
  const { context, componentInstance } = vnode
  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.data.keepAlive是true,当组件已经mounted过则执行queueActivatedComponent,否则执行activateChildComponent。

queueActivatedComponent

看到这个queueActivatedComponent是不是特别眼熟,哈哈哈,因为在看Watcher队列的时候见过,这里简单看下。它定义在src/core/observer/scheduler.js

export function queueActivatedComponent (vm: Component) {
  // setting _inactive to false here so that a render function can
  // rely on checking whether it's in an inactive tree (e.g. router-view)
  vm._inactive = false
  activatedChildren.push(vm)

将当前vm实例加入到activatedChildren数组中,在下一个Tick执行flushSchedulerQueue时会调用callActivatedHooks,其内部还是调用了activatedChildComponent。

所以缓存组件最后都会执行activatedChildComponent,只是如果组件已经mounted,会延后去执行。

function flushSchedulerQueue () {
  ...
  
  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}

function callActivatedHooks (queue) {
  for (let i = 0; i < queue.length; i++) {
    queue[i]._inactive = true
    activateChildComponent(queue[i], true /* true */)
  }
}

接下来看下activatedChildComponent具体做了什么??

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
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

这里就是activated钩子的执行时机,会递归去执行所有子组件的activated钩子。

deactivated钩子

既然文档说deactivated钩子是在组件失活时调用,可以猜到是在destroy中。定义在src/core/vdom/create-component.js

destroy (vnode: MountedComponentVNode) {
  const { componentInstance } = vnode
  if (!componentInstance._isDestroyed) {
    if (!vnode.data.keepAlive) {
      componentInstance.$destroy()
    } else {
      deactivateChildComponent(componentInstance, true /* direct */)
    }
  }
}
  • 如果组件Vnode不是缓存组件则直接调用$destroy方法

  • 如果是缓存组件则调用deactivateChildComponent方法

deactivateChildComponent

deactivateChildComponent定义在src/core/instance/lifecycle.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')
  }
}

写法与activated钩子类似,都是递归去调用所有子组件的deactivated钩子。

后记

Vue在执行渲染的patch函数中最后一步会执行invokeInsertHook函数,对于缓存组件,就是这个时机去触发activated钩子的。

具体的钩子函数实现后边专门细看~