Skip to content

原理

根据组件的tag和keys来生成具有LRU的缓存的map,通过添加abstract属性,避免出现在$parent链中,每次渲染时总是检查缓存是否存在,如果存在render函数直接返回之前的vnode并添加缓存。返回新的vnode后在keep-alive的patch阶段又会重新走createComponent子组件的操作,就会进入之前的缓存逻辑

流程

在当前实例中vnode如果是是组件(keep-alive),createComponentInstanceForVnode(),示例化keep-alive。keep-alive在初始化中总是绘制触发render-watcher(this.value = this.lazy ? undefined : this.get())。触发了updateComponent(vm._render()), 也就是renderkeep-alive的render函数()

keep-alive render中,总是检查this.$slots.default(总是一个vnode)的name是否在keep-alive库中,如果在库中检查是否缓存了vnode,没有则缓存,如果有则将componentInstance赋值到组件上,添加vnode.data.keep-alive = true

在keep-alive 的 createComponent 阶段,由于data.keepAlive 存在并且组件实例存在,直接进入prepatch 阶段,不需要走后面的子组件挂载等操作, 否则按照正常的组件流程走创建子组件挂载等。

源码

组件的源码比较简单,有两个需要主要的点吗,设置abstract:true 还是能通过parent属性获取到keep-alive组件示例,对于手动的修改缓存队列或者在自定义缓存时有用。keep-alive的render实际是返回一个vnode,如果能够根据key查到缓存则返回之前的vnode。

js
// keep-alive.vue
render() {
  const slot = this.$slots.default
  const vnode: VNode = getFirstComponentChild(slot) // 找到第一个子组件对象
  const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
  if (componentOptions) { // 存在组件参数
    // check pattern
    const name: ?string = getComponentName(componentOptions) // 组件名
    const { include, exclude } = this
    if ( // 条件匹配
      // not included
      (include && (!name || !matches(include, name))) ||
      // excluded
      (exclude && name && matches(exclude, name))
    ) {
      return vnode
    }

    const { cache, keys } = this
    const key: ?string = vnode.key == null // 定义组件的缓存key
      // same constructor may get registered as different local components
      // so cid alone is not enough (#3269)
      ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
      : vnode.key
    if (cache[key]) { // 已经缓存过该组件
      vnode.componentInstance = cache[key].componentInstance
      // make current key freshest
      remove(keys, key)
      keys.push(key) // 调整key排序
    } else {
      cache[key] = vnode // 缓存组件对象
      keys.push(key)
      // prune oldest entry
      if (this.max && keys.length > parseInt(this.max)) { // 超过缓存数限制,将第一个删除
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
    }

    vnode.data.keepAlive = true // 渲染和执行被包裹组件的钩子函数需要用到
  }
  return vnode || (slot && slot[0])
}

核心在下面的createComponent中,针对keepAlive包裹的组件,在重新渲染时vnode因为是之前的vnode,会触发init额外的流程。进而直接到prepatch阶段

js
// vnode指的是根据keep-alive render函数得到的包裹组件的vnode,拿到vnode。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
      var i = vnode.data;
      if (isDef(i)) {
        // isReactivated用来判断组件是否缓存。
        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            // 执行组件初始化的内部钩子 init,下面的componentVNodeHooks.init
          i(vnode, false /* hydrating */);
        }
        if (isDef(vnode.componentInstance)) {
          // 其中一个作用是保留真实dom到vnode中
          initComponent(vnode, insertedVnodeQueue);
          insert(parentElm, vnode.elm, refElm);
          if (isTrue(isReactivated)) {
            reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
          }
          return true
        }
      }
}

    var componentVNodeHooks = {
    init: function init (vnode, hydrating) {
      if (
        vnode.componentInstance &&
        !vnode.componentInstance._isDestroyed &&
        vnode.data.keepAlive
      ) {
        // kept-alive components, treat as a patch
        var mountedNode = vnode; // work around flow
        componentVNodeHooks.prepatch(mountedNode, mountedNode);
      } else {
          // 将组件实例赋值给vnode的componentInstance属性
        var child = vnode.componentInstance = createComponentInstanceForVnode(
          vnode,
          activeInstance
        );
        child.$mount(hydrating ? vnode.elm : undefined, hydrating);
      }
    },
    // 后面分析
    prepatch: function prepatch (oldVnode, vnode) {
        // 新组件实例
        var options = vnode.componentOptions;
        // 旧组件实例
        var child = vnode.componentInstance = oldVnode.componentInstance;
        updateChildComponent(
            child,
            options.propsData, // updated props
            options.listeners, // updated listeners
            vnode, // new parent vnode
            options.children // new children
        );
    }
}