什么是 keep-alive
?
<keep-alive>
是一个内置组件,用于缓存组件实例,从而提高应用的性能。当包裹动态组件时,<keep-alive>
会缓存不活跃的组件实例,而不是销毁它们。这使得当组件重新激活时,可以保留其状态,避免重新渲染,从而提升用户体验和性能。
主要用途
- 缓存组件状态:当组件在不同路由之间切换时,
<keep-alive>
可以缓存组件的状态,避免每次切换时重新渲染。 - 优化性能:通过缓存组件实例,减少不必要的 DOM 操作和计算,提高应用的响应速度。
基本用法:
<template>
<div>
<button @click="activeComponent = 'ComponentA'">Component A</button>
<button @click="activeComponent = 'ComponentB'">Component B</button>
<keep-alive>
<component :is="activeComponent"></component>
</keep-alive>
</div>
</template>
<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
export default {
components: {
ComponentA,
ComponentB
},
data() {
return {
activeComponent: 'ComponentA'
};
}
};
</script>
在这个例子中,<keep-alive>
包裹了 <component>
,当 activeComponent
切换时,组件实例会被缓存而不是销毁。
- **
include
和exclude
**:用于控制哪些组件需要缓存,支持字符串、正则表达式或数组。
<keep-alive include="ComponentA, ComponentB" exclude="ComponentC">
<router-view></router-view>
</keep-alive>
2. **
max
**:用于指定缓存的组件数量,当超出这个数量时,最久未使用的组件实例将被销毁。
<keep-alive :max="10">
<router-view></router-view>
</keep-alive>
与 <router-view>
一起使用:
<template>
<div>
<router-link to="/a">Component A</router-link>
<router-link to="/b">Component B</router-link>
<keep-alive :include="['ComponentA']" :max="10">
<router-view></router-view>
</keep-alive>
</div>
</template>
keep-alive
的源码分析
export default {
name: 'KeepAlive',
abstract: true, // 这是一个抽象组件,表示它不会直接渲染到 DOM 上
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);
}
},
watch: {
include (val) {
pruneCache(this, name => matches(val, name));
},
exclude (val) {
pruneCache(this, name => !matches(val, name));
}
},
render () {
const slot = this.$slots.default;
const vnode = getFirstComponentChild(slot); // 获取第一个子组件
if (vnode) {
const componentOptions = vnode.componentOptions;
const name = getComponentName(componentOptions);
if (name && (
(this.include && !matches(this.include, name)) ||
(this.exclude && matches(this.exclude, name))
)) {
return vnode; // 如果不匹配 include/exclude,直接返回,不缓存
}
const key = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key;
if (this.cache[key]) {
vnode.componentInstance = this.cache[key].componentInstance; // 从缓存中取出实例
remove(this.keys, key); // 移除旧的位置
this.keys.push(key); // 重新放到最后,更新 LRU 位置
} else {
this.cache[key] = vnode; // 缓存新实例
this.keys.push(key);
// 如果超过最大缓存数,移除最早的实例
if (this.max && this.keys.length > parseInt(this.max)) {
pruneCacheEntry(this.cache, this.keys[0], this.keys, this._vnode);
}
}
vnode.data.keepAlive = true; // 标记组件为 keep-alive
}
return vnode || (slot && slot[0]); // 返回 vnode
}
};