重读 vue 文档 --- keep-alive

1,977 阅读3分钟

引言

keep-alive 是 vue 内置的抽象组件,可以用于包裹需要缓存的组件。保存组件状态,避免重新渲染,增强性能。

Props

keep-alive 组件接收三个参数,分别为 includeexcludemax

  • include - 数组、字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 数组、字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

使用

结合动态组件使用

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。

结合 vue-router 使用

<keep-alive>
    <router-view>
        <!-- 所有路径匹配到的视图组件都会被缓存! -->
    </router-view>
</keep-alive>

缓存指定路由对应组件

<keep-alive>
    <router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

// vue-router routes 配置
export default [
  {
    path: '/',
    name: 'componentsName',
    component: componentsName,
    meta: {
      keepAlive: true // 需要被缓存
    }
  }
]

<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。

深入源码

export default {
  name: 'keep-alive',
  abstract: true, // 抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
  // 具体关于抽象组件不会渲染出DOM,在 src/core/instance/lifecycle.js

  props: {
    include: patternTypes, // [String, RegExp, Array]
    exclude: patternTypes,
    max: [String, Number]
  },

  created () {
    this.cache = Object.create(null) // 新建缓存对象 { key: vnode }
    this.keys = [] // 按序保存组件的 key
  },

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

  /**
  * 监听 include、exclude 动态变化
  */
  mounted () {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name)) // function filter (name) { return matches(val, name); } 
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    const slot = this.$slots.default // 获取插槽默认内容
    const vnode: VNode = getFirstComponentChild(slot) // 获取第一个子节点
    // <keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this
      /**
       * 未匹配到相应组件,不缓存
       */
      if (
        (include && (!name || !matches(include, name))) ||
        (exclude && name && matches(exclude, name)) 
      ) {
        return vnode
      }

      const { cache, keys } = this
      // 生成 key
      const key: ?string = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      if (cache[key]) {
        // 命中缓存
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest 刷新 keys 数组
        remove(keys, key)
        keys.push(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])
  }
}

在 keep-alive 组件创建时,新建 catch 缓存节点,keys 按序保存key。通过传入的 include、exclude判断是否命中缓存,命中,则从缓存中拿vnode组件实例,调整key的顺序。未命中,则添加缓存。判断是否超出最大缓存数量,超出,则删除最久未被使用的节点,即keys第一个 key 对应的 vnode

生命周期

组件一旦被 缓存,再次渲染就不会执行 created、mounted生命周期。因此 vue 提供了 activated和 deactivated 两个生命周期函数,在缓存组件再次渲染时执行一些操作。

总结

<keep-alive>组件通过插槽,获取第一个子节点。根据 include、exclude判断是否需要缓存,通过组件的 key,判断是否命中缓存。利用LRU算法,更新缓存以及对应的 keys 数组。根据 max控制缓存的最大组件数量。

参考