小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金
前言
【一天一个小知识,每天进步一点点】小伙伴们大家好。上一篇文章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两个属性进行缓存。当下次子组件被重新渲染时首先判断缓存列表中是否存在,如果存在则直接从缓存中拉取,否则暂存在待缓存对象中待下次更新时缓存。 喜欢的小伙伴欢迎点赞留言加关注哦!