vue-keep-alive源码分析

1,033 阅读3分钟

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

注:以下是个人理解、如有不对还望指正!

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中当组件在 内被切换,组件在活跃和不活跃触发activated 和 deactivated 这两个生命周期钩子函数

keep-alive使用

  • 需求 完成对一个组件进行缓存配置
<div id="app">
    <keep-alive include="test">
        <test></test>
        <demo></demo>
    </keep-alive>
</div>

const test = {
    name:'test',
    template:"<div> {{ componentsName }} </div>",
    data(){
        return {
            componentName:'test测试组件'
        }
    },
    activated(){
        console.log('test-activated')
    }
}

const demo = {
    name:'demo',
    template:"<div> {{ name }} </div>",
    data(){
        return {
            componentName:'demo测试组件'
        }
    },
    activated(){
        console.log('demo_keep-alive')
    }
}

const vm = new Vue({
    components:{
        test,
        demo
    },
    el:'#app'
})

我们发现页面只会对第一个test组件进行展示、奇怪了为什么我只有test呢、那demo组件去哪里了呢?带着这些疑问我们打开vue的源码看下知其所以然....

源码分析

我会把主要源码贴入进来、包括代码的注释、组件文件地址:/src/core/components/keep-alive.js

  • 判断配置和当前组件名称是否匹配
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}
  • 匹配条件、清除缓存
function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions)
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}
  • 创建缓存对象和保存组件key
created () {
    this.cache = Object.create(null)
    this.keys = []
 }
  • watch监听配置变化
mounted () {
    //监听配置、重置缓存信息
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  }
  • 执行render方法
//执行页面render
  render () {
    const slot = this.$slots.default
    //获取kepp-alive组件下的第一个子组件vndoe
    const vnode: VNode = getFirstComponentChild(slot)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // 获取组件名称
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this;
      //判断是否是需要缓存、不需要直接走这if
      if (
        // 有include和没有获取到name值 或者 include是否包含name值
        (include && (!name || !matches(include, name))) ||
        // 是否是白名单、直接过滤
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }
      //需要缓存逻辑
      const { cache, keys } = this
      //判断是否有key、如果没有vue会自动给他加上key
      const key: ?string = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      //当前是否已经有缓存下来的组件数据、有直接取缓存的
      if (cache[key]) {
          //赋值缓存的vnode
        vnode.componentInstance = cache[key].componentInstance
        // 重新调整组件key的位置、目的是为了如果数据最近被访问过,那么将来被访问的几率也更高
        remove(keys, key)
        keys.push(key)
      } else {
        //保存缓存vnode数据
        cache[key] = vnode
        //添加key
        keys.push(key)
        // 判断是否超过最大缓存值
        if (this.max && keys.length > parseInt(this.max)) {
          //超过就删除第一个保存的vnode
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }
      //添加keepAlive = true标记
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }

总结

大致的缓存流程,vue有个内置组件、这个组件维护着缓存对象和被缓存组件的key名称、提高你传入的include, exclude判断是否需要缓存当前组件、而且我们在render函数发现组件永远都只会取第一个子组件内容、而我们案例上的demo组件永远没有机会显示出来、其实也有办法那就是给他们包裹vue提供的另外一个内置组件component、判断显示那个组件、然后keep-alive会提高当前组件是否设置了白名单或者不是include配置项组件那就直接return vnode,遇到缓存的vnode、先判断缓存对象是否已经存如果存在直接取缓存vnode (有个小细节、重新调整组件key的位置、目的是为了如果数据最近被访问过,那么将来被访问的几率也更高、因为可能缓存到一定max数量、会遇到删除栈的vnode、这个时候是根据key的位置在操作的) 、不存在的话往缓存对象添加记录