Vue2.0源码解析 - 知其然知其所以然之keep-alive原理分析(二)

前言

【一天一个小知识,每天进步一点点】小伙伴们大家好。上一篇文章Vue2.0源码解析 - 知其然知其所以然之keep-alive原理分析(一)中已经对keep-alive源码的整体框架了做了一个简单的梳理,知道了keep-alive工作的一个大体流程,今天我们我将继续对keep-alive一些核心的源码进行展开分析。

cacheVNode

上篇文章中提到了在methods中定义了一个cacheVNode函数,用来做虚拟DOM的缓存,下面就来分析一下它的源码:

methods:{
	cacheVNode(){
		const {cache, keys, vnodeToCache, keyToCache} = this, 
		if (vnodeToCache){
			const {tag, componentInstance, componentOptions } = vnodeToCache
			cache[keyToCache] = {
				name: getComponentName(componentOptions),
				tag,
				componentInstance,
			}
			keys.push(keyToCache)
			if(this.max && keys.length > parseInt(this.max)){
				pruneCacheEntry(cache, keys[0], keys, this._vnode)
			}
			this.vnodeToCache = null;
		}
	}
}
  • 首先获取实例上的四个属性cache, keys, vnodeToCache, keyToCache
    • 前两个属性已经在上一篇文章中有说明,这里不再赘述
    • vnodeToCache需要被缓存的虚拟dom
    • keyToCache需要被缓存的虚拟dom对应的key
  • 如果vnodeToCache不为空则说明需要被缓存,结构出vnodeToCache的详细信息,并保存在以keyToCache为键的对象cache中
  • 同时将键keyToCache保存在keys列表中
  • 判断是否设置了缓存最大值,如果设置了并且缓存列表的长度已经超过了最大值,则执行pruneCacheEntry函数对缓存列表进行修剪,注意这里是将最早缓存的组件进行删除。
  • 最后将vnodeToCache 清空

getComponentName

组件的公共函数,用于获取组件的名称

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

pruneCacheEntry

组件的公共函数

function pruneCacheEntry (
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  // cached 有值,并且 不是当前正在渲染的 组件
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}
  • 从cache对象中获取到要被销毁的组件实例
  • 如果存在并且不是当前正在渲染的组,则直接销毁
  • 同时从cache对象中移除虚拟dom实例
  • 从keys列表中移除对应的key

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
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
	    // delay setting the cache until update
    	this.vnodeToCache = vnode
       	this.keyToCache = key
      }
      //添加keepAlive = true标记
      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }

  • 拿到keep-alive组件中的默认插槽,并根据插槽内容获取到keep-alive的第一个组件。前面的文章也提到过,即便是由多个子组件,默认也只有第一个是有效的。
  • 获取到子组件的名字name,并根据keep-alive的include和exclude去判断当前子组件是否被缓存,出现以下两种情况则不走缓存,直接将vnode返回
    • include:如果include属性存在,并且name不包含在include中
    • exclude:如果include属性存在,并且那么包含在exc中
  • 如果都不符合上述两种条件,则说明需要走缓存逻辑并从当前keep-alive实例中取出缓存对象cache和对应的键列表keys
  • 取出vnode的key,如果key不存在则重新生成一个
  • 根据key到cache对象中判断要被渲染的子组件是否被缓存
    • 如果cache对象中存在该子组件的缓存,则直接取出缓存对象的componentInstance(子组件实例)并赋值给vnode的componentInstance;同时将key从keys列表中移除并重新添加(为了保证当前key是最新的)
    • 如果子组件没有被缓存,则把vnode和key分别赋值给keep-alive实例的vnodeToCache和keyToCache两个属性中,等待下次更新时进行缓存
  • 最后设置keepAlive属性为true,并将当前vnode返回渲染。

总结

关于keep-alive的原理分析到这里就结束了。简单的总结下:就是在使用keep-alive时,会根据include和exclude两个属性来判断,keep-alive内部的子组件是否满足匹配条件,如果满足则把子组件对应的虚拟dom的实例利用cache和keys两个属性进行缓存。当下次子组件被重新渲染时首先判断缓存列表中是否存在,如果存在则直接从缓存中拉取,否则暂存在待缓存对象中待下次更新时缓存。
喜欢的小伙伴欢迎点赞留言加关注哦!

你可能感兴趣的:(VUE,小知识,keep-alive,vue缓存,keep-alive原理,keep-alive源码)