keep-alive
是一个内置组件,用于缓存动态组件或组件的状态,以避免重复渲染和销毁。它通常用于优化性能,尤其是在需要频繁切换组件的场景中(如 Tab 切换、路由切换等)。
export default {
name: "keep-alive",
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
},
created() {
this.cache = Object.create(null); // 缓存在内存中
this.keys = [];
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted() {
this.$watch("include", (val) => {
pruneCache(this, (name) => matches(val, name));
});
this.$watch("exclude", (val) => {
pruneCache(this, (name) => !matches(val, name));
});
},
render() {
const slot = this.$slots.default;
const vnode: VNode = getFirstComponentChild(slot); // 获取子组件的 VNode
const componentOptions: ?VNodeComponentOptions =
vnode && vnode.componentOptions;
if (componentOptions) {
// check pattern
const name: ?string = getComponentName(componentOptions);
const { include, exclude } = this;
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode;
}
const { cache, keys } = this;
const key: ?string =
vnode.key == null
? // 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
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
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]);
},
};渲染逻辑
在 render 函数中, 会获取子组件的 key,并检查是否已经缓存。
如果已经缓存,则直接从 cache 中取出组件实例。
如果没有缓存,则创建新的组件实例,并将其存入 cache。
keep-alive 组件的实现也是一个对象, 它有一个属性 abstract 为 true,是一个抽象组件, vue 的文档没有提这个概念,实际上它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle
keep-alive 在 created 钩子里定义 this.cache 和 this.keys, 本质就是去缓存已经创建过的 vnode。 keep-alive 直接实现了 render 函数,而不是我们常规模版的方式,执行 keep-alive 组件渲染的时候,就会执行到这个 render 函数。
首先获取第一个子元素的 vnode
matches 的逻辑很简单,就是做匹配,分别处理了数组、字符串、正则表达式的情况,也就是说我们平时传的 include 和 exclude 可以是这三种类型的任意一种。并且我们的组件名如果满足了配置 include 且不匹配或者是配置了 exclude 且匹配,那么就直接返回这个组件的 vnode,否则的话走下一步缓存:
如果命中缓存,则直接从缓存中拿 vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个;否则把 vnode 设置进缓存,最后还有一个逻辑,如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
组件渲染
我们关注 2 个方面,首次渲染和缓存渲染。
组件的 patch 过程会执行 createComponent 方法
第一次渲染的时候,vnode.componentInstance 为 undefined,vnode.data.keepAlive 为 true,因为它的父组件 <keep-alive> 的 render 函数会先执行,那么该 vnode 缓存到内存中,并且设置 vnode.data.keepAlive 为 true,因此 isReactivated 为 false,那么走正常的 init 的钩子函数执行组件的 mount。当 vnode 已经执行完 patch 后,执行 initComponent 函数:
缓存渲染
当数据发送变化,在 patch 的过程中会执行 patchVnode 的逻辑,它会对比新旧 vnode 节点,甚至对比它们的子节点去做更新逻辑,但是对于组件 vnode 而言,是没有 children 的,patchVnode 在做各种 diff 之前,会先执行 prepatch 的钩子函数。
prepatch 核心逻辑就是执行 updateChildComponent 方法
updateChildComponent 方法主要是去更新组件实例的一些属性,这里我们重点关注一下 slot 部分,由于 <keep-alive> 组件本质上支持了 slot,所以它执行 prepatch 的时候,需要对自己的 children,也就是这些 slots 做重新解析,并触发 <keep-alive> 组件实例 $forceUpdate 逻辑,也就是重新执行 <keep-alive> 的 render 方法,这个时候如果它包裹的第一个组件 vnode 命中缓存,则直接返回缓存中的 vnode.componentInstance
isReactivated 如果为 true, 则执行 reactivateComponent 方法 并且在执行 init 钩子函数的时候不会再执行组件的 mount 过程
回到 createComponent 方法,在 isReactivated 为 true 的情况下会执行 reactivateComponent 方法
如果是被 <keep-alive> 包裹的组件已经 mounted,那么则执行 queueActivatedComponent(componentInstance) ,否则执行 activateChildComponent(componentInstance, true)。我们先分析非 mounted 的情况,activateChildComponent 的定义在 src/core/instance/lifecycle.js 中
在我们 initlifecycle 中, 我们会将 directInactive 设置为 false,接着进入 activateChildComponent 方法中
如果已经 mounted 的方法会执行 queueActivedComponent 方法
销毁
keep-alive 组件销毁的时候会执行 deactivateChildComponent 方法
总结: 通过自定义 render 函数并且利用了插槽 当命中缓存,则不会执行 created 和 mounted 钩子函数, 而会执行 activated 钩子 销毁 deactivated 且在 patch 过程中对于已缓存的组件不会执行 mounted,所以不会有一般的组件的生命周期函数但是又提供了 activated 和 deactivated 钩子函数。另外我们还知道了 的 props 除了 include 和 exclude 还有文档中没有提到的 max,它能控制我们缓存的个数。
lru 算法
LRU ( Least Recently Used :最近最少使用 )缓存淘汰策略,故名思义,就是根据数据的历史访问记录来进行淘汰数据,其核心思想是 如果数据最近被访问过,那么将来被访问的几率也更高 ,优先淘汰最近没有被访问到的数据。
原理
keep-alive 在 vue 中用于实现组件的缓存,当组件切换时不会对当前组件进行卸载.
最常用的两个属性: include, exclude, 用于组件进行有条件的缓存, 可以用都好分割字符串、 正则表达式或一个数组来表示。
在 2.5.8 版本中, keep-alive 新增了 max 属性, 用于最多可以缓存多少组件实例, 一旦这个数字达到了, 在新实例被创建之前, 已缓存组件中最久没有被访问的实例会被注销掉。这里用到了 lru 算法。
在 keep-alive 缓存超过 max 时,使用的缓存淘汰算法就是 LRU 算法,它在实现的过程中用到了 cache 对象用于保存缓存的组件实例及 key 值,keys 数组用于保存缓存组件的 key ,当 keep-alive 中渲染一个需要缓存的实例时:
判断缓存中是否已缓存了该实例,缓存了则直接获取,并调整 key 在 keys 中的位置(移除 keys 中 key ,并放入 keys 数组的最后一位) 如果没有缓存,则缓存该实例,若 keys 的长度大于 max (缓存长度超过上限),则移除 keys[0] 缓存。
最后更新于
这有帮助吗?