keep-alive源码笔记一

vue版本: 2.6+

  1. 缓存什么
    keep-alive 只会缓存第一个子组件,缓存的组件实例里也会包含孙子组件的数据,所以被缓存组件中的孙子组件的状态也会被记住(别的组件中若用到同一个孙子组件是不会被缓存的);
  2. 如何缓存
    keep-alive 如果没有 include,exclude 默认缓存所有匹配到的组件; 组件实例会被缓存到cache中,优先以组件key值,作为缓存数据的key值;(若无key则由组件实例的 tag 和 cid 来组合)
    
    	
    
    ---
    cache.111 中存放组件的缓存数据
    
  3. 如何销毁
    keep-alive 销毁时才会销毁缓存的数据;

注意1:exclude 的优先级高于 include,也就是说如果 include,exclude 都包含某个 name,将不会缓存该name对应的组件;

注意2:activated 钩子是在被缓存组件内部所有的子组件mounted执行之后调用,为了避免activated获取到错误的组件实例;

注意3:如果不写 name, 可以给组件名设置规则以 cached- 开头的组件会被缓存;

// 缓存以 cache- 开头的组件

	...

下面是源码注释

渲染keep-alive先触发他的render,所以从render函数看起,为了好看可以粘贴到vscode中看

/* @flow */
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

type CacheEntry = {
  name: ?string;
  tag: ?string;
  componentInstance: Component;
};

type CacheEntryMap = { [key: string]: ?CacheEntry };

function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}

function matches (pattern: string | RegExp | Array, 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
}

// 删除不满足filter的缓存
function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const entry: ?CacheEntry = cache[key]
    if (entry) {
      const name: ?string = entry.name
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

function pruneCacheEntry (
  cache: CacheEntryMap,
  key: string,
  keys: Array,
  current?: VNode
) {
  const entry: ?CacheEntry = cache[key]
  if (entry && (!current || entry.tag !== current.tag)) {
    entry.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

const patternTypes: Array = [String, RegExp, Array]

export default {
  name: 'keep-alive',
  abstract: true,

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

  methods: {
    // 设置keys,cache, 超过max的要把第一个删掉,每次update只会更新一个缓存,所以删除一个就够了
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache
        cache[keyToCache] = {
          name: getComponentName(componentOptions),
          tag,
          componentInstance,
        }
        keys.push(keyToCache)
        // 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 () {
    // 销毁keep-alive清楚所有的缓存
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    // 设置keys,cache, 超过max的要把第一个删掉,每次update只会更新一个缓存,所以删除一个就够了
    this.cacheVNode()

    // i中有about,在e中加上about,删会删除about
    // e中有about,include中加上about,也不会增加about的缓存
    // i,e都没有,在exclude加上会删除about的缓存
    // 综上所述exclude的优先级比include高
    // 删除不在include中的缓存 about
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })

    // 删除在exclude中的缓存 about
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  updated () {
    this.cacheVNode()
  },
	
  render () {
    const slot = this.$slots.default
    // 获取第一个子组件的vnode
    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不存在或include不包含该name
        (include && (!name || !matches(include, name))) ||
        // excluded
        // exclude 存在 且name存在而且在exclude中
        (exclude && name && matches(exclude, name))
      ) {
        // 直接返回vnode不做缓存
        return vnode
      }
      
      // 否则缓存
      const { cache, keys } = this
      const key: ?string = vnode.key == null
        // 相同的构造函数可能创建不同的本地组件,
        // 所以仅仅是cid是不够的
        // function getComponentName (opts: ?VNodeComponentOptions): ?string {
        //   return opts && (opts.Ctor.options.name || opts.tag)
        //   组件的tag也有可能是undefined,这个暂时未知
        // }
        // 所以key的规则是,1. 优先是key属性值,然后是2. cid 或是 3. cid::tag
        // 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

      // 如果该组件已经有缓存把数据换上去,把key挪到keys的最后面,因为max是从前面开始删除的
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)

      } else {
        // 否则延迟设置缓存直到update钩子触发, 应该是子组件的依赖更新会触发keepAlive的updated
        // delay setting the cache until update
        this.vnodeToCache = vnode
        this.keyToCache = key
      }

      // 设置被缓存的组件的data.keepAlive为true
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

你可能感兴趣的:(vue,vue.js,缓存,前端)