lostvita / blog

60 stars 47 forks source link

彻底揭秘keep-alive #4

Open lostvita opened 5 years ago

lostvita commented 5 years ago

前言

本文介绍的内容包括:

keep-alive在它生命周期内定义了三个钩子函数:

简单描述从renderpatch的过程 我们从最简单的new Vue开始:

import App from './App.vue'

new Vue({
  render: h => h(App),
}).$mount('#app')

用一张图表达: image

keep-alive组件的渲染

我们用过keep-alive都知道,它不会生成真正的DOM节点,这是怎么做到的?

// src/core/instance/lifecycle.js
export function initLifecycle (vm: Component) {
  const options = vm.$options
  // 找到第一个非abstract的父组件实例
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  vm.$parent = parent
  // ...
}

Vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在keep-alive中,设置了abstract: true,那Vue就会跳过该组件实例。 最后构建的组件树中就不会包含keep-alive组件,那么由组件树渲染成的DOM树自然也不会有keep-alive相关的节点了。

keep-alive包裹的组件是如何使用缓存的?patch阶段,会执行createComponent函数:

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const 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)
        insert(parentElm, vnode.elm, refElm) // 将缓存的DOM(vnode.elm)插入父元素中
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }