Vue 2.6源码学习(07)-keep-alive的使用和源码介绍

83 阅读2分钟

keep-alive的使用和源码介绍

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。

<!-- 基本使用 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>

include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示 max:最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

export default {
  name: 'keep-alive',// 组件名
  abstract: true, // 抽象组件 就是不会被记录到 $children 和 $parent上

  /**
  include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  max - 数字。最多可以缓存多少组件实例。
  */
  props: { 
    include: patternTypes, // 可以缓存哪些组件 ['a','b']
    exclude: patternTypes, // 可以排除哪些组件 ['a']
    max: [String, Number] // 最大缓存个数  6
  },

  methods: {
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache
        cache[keyToCache] = { // 缓存中 防止需要缓存的组件 。 存放组件的实例
          name: getComponentName(componentOptions),
          tag,
          componentInstance, // 组件实例渲染过会有$el 属性,下次可以直接复用$el属性
        }
        keys.push(keyToCache) // 放入key 
        // prune oldest entry // 超过最大长度会移除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
        this.vnodeToCache = null
      }
    }
  },

  created () {
    this.cache = Object.create(null) // 弄了个缓存区 {}
    this.keys = [] // 缓存组件的名字有哪些 []
  },

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    this.cacheVNode() // 渲染完后 缓存虚拟节点
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  updated () {
    this.cacheVNode()
  },

  render () {
    const slot = this.$slots.default // 取的是默认插槽
    const vnode: VNode = getFirstComponentChild(slot) // 获取插槽中的第一个虚拟节点

    // { Ctor, propsData, listeners, tag, children }
    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   // {}  []
      // 生成一个唯一的key
      const key: ?string = vnode.key == null
        // 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]) { // 是否这个key缓存过 
        vnode.componentInstance = cache[key].componentInstance // 获取缓存的实例 
        // make current key freshest
        remove(keys, key) // 把当前的key 作为最新的
        keys.push(key)
      } else {
        // delay setting the cache until update
        this.vnodeToCache = vnode // 缓存当前的vnode 
        this.keyToCache = key // 这个key也要缓存
      }

      vnode.data.keepAlive = true // 给虚拟节点增加了一个标识 data: keep-alive
    } 
    // vnode上有data.keepAlive  和 我们的 componentInstance 说明这个虚拟节点是缓存过的
    return vnode || (slot && slot[0]) // 最终返回vnode
  }
}

小结

<keep-alive>缓存不活动的组件实例, 不会在函数式组件中正常工作,因为它们没有缓存实例。