大部分摘抄至https://juejin.cn/post/7511568225987051555?searchId=202508041118462C6329C8A295609FB176#heading-36
keep-alive
<template>
<keep-alive :include="['ComponentA', 'ComponentB']" :max="5">
<component :is="currentComponent"></component>
</keep-alive>
</template>
一、使用场景和核心作用
1.作用:
1.1 缓存组件实例 ,避免重复销毁和创建,保留组件状态(如DOM结构、响应式数据、事件监听)
(Vue 等前端框架中,组件实例(Component Instance) 是指组件被渲染到页面时,框架根据组件定义(如模板、数据、方法等)创建的一个具体运行时对象,它包含了组件运行所需的所有信息和状态,是组件在页面中 “存活” 的具体形态
可以简单理解为:组件定义是 “设计图纸”,而组件实例是根据图纸造出的 “实物”,这个实物拥有自己的属性、行为和生命周期。
组件实例包含的核心内容:DOM 结构,响应式数据data、props....,方法与时间监听methods、watch、@click...,生命周期状态)
1.2 提升性能:适用于需要频繁切换但状态需保留的组件(如Tab页、表单填写页)
场景: tab 切换,表单填写页
二、生命周期钩子变化
- 新增钩子(仅在被缓存的组件中触发)
onActivated:组件被激活(插入DOM)时触发。
onDeactivated:组件被停用(移除DOM)时触发 - 执行顺序:
首次加载:onCreate->onMounted->onActivated
切换离开:onDeactivated
再次进入:onActivated
彻底销毁:onUnmounted
三、关键配置属性
1.include
匹配组件名称(name选项),仅缓存匹配的组件
支持字符串、正则、数组
<keep-alive :include="/^Test/">
2.exclude
排除指定组件,优先级高于include
3.max
最大缓存实例数,超出时按LRU(最近最少使用)策略淘汰旧实例
LUR原理:有限淘汰最久未访问的实例
四、高频面试题
keep-alive实现原理
1、缓存机制:通过Map或Object缓存组件vnode实例,渲染时直接从缓存中取

1.1使用 Object 缓存组件
通过对象的键值对特性,将组件实例存储在对象中:
import { defineComponent, h, ref } from 'vue'
export default defineComponent({
setup() {
// 用于缓存组件的对象
const componentCache = {}
// 需要渲染的组件列表
const components = ref([
{ id: 'comp1', type: 'ComponentA', props: { msg: '组件1' } },
{ id: 'comp2', type: 'ComponentB', props: { msg: '组件2' } }
])
// 获取或创建组件实例
const getComponent = (item) => {
// 如果缓存中存在,直接返回
if (!componentCache[item.id]) {
// 根据类型动态导入组件
const Component = item.type === 'ComponentA'
? defineComponent({
props: ['msg'],
render() { return h('div', this.msg) }
})
: defineComponent({
props: ['msg'],
render() { return h('div', this.msg + ' (B类型)') }
})
// 创建组件实例并缓存
componentCache[item.id] = h(Component, { ...item.props })
}
return componentCache[item.id]
}
return () => h('div', components.value.map(item => getComponent(item)))
}
})
1.2使用 Map 缓存组件
Map 相比 Object 更适合作为缓存容器,因为它支持各种类型的键,且有更丰富的操作方法:
import { defineComponent, h, ref } from 'vue'
export default defineComponent({
setup() {
// 用于缓存组件的Map
const componentCache = new Map()
// 需要渲染的组件列表
const components = ref([
{ id: 'comp1', type: 'ComponentA', props: { msg: '组件1' } },
{ id: 'comp2', type: 'ComponentB', props: { msg: '组件2' } }
])
// 获取或创建组件实例
const getComponent = (item) => {
// 使用id作为Map的键
if (!componentCache.has(item.id)) {
// 动态创建组件
const Component = item.type === 'ComponentA'
? defineComponent({
props: ['msg'],
render() { return h('div', this.msg) }
})
: defineComponent({
props: ['msg'],
render() { return h('div', this.msg + ' (B类型)') }
})
// 缓存组件实例
componentCache.set(item.id, h(Component, { ...item.props }))
}
return componentCache.get(item.id)
}
return () => h('div', components.value.map(item => getComponent(item)))
}
})
两者区别
- 键类型支持:
Map:键可以是任意 JavaScript 类型,包括对象、函数等。例如,可以使用组件实例本身或其他复杂对象作为键来缓存相关组件,这在需要更灵活的键值关联场景中非常有用。
Object:键只能是字符串类型和 Symbol 类型。如果需要以非字符串或 Symbol 类型作为键来缓存组件,就无法直接使用 Object。 - 插入顺序保留:
Map:严格保留键值对的插入顺序。当迭代 Map 时,会按照插入的顺序返回键值对,这对于需要按照特定顺序处理缓存组件的场景很重要,比如依次渲染缓存的组件。
Object:在 ES6 之后,对象的键也是有序的,但在早期 JavaScript 实现中,对象键的顺序是不可靠的。因此,如果依赖键的顺序,使用 Object 缓存组件可能会出现意外情况。 - 属性数量获取:
Map:可以通过.size 属性直接获取其中键值对的数量,操作简单高效。
Object:需要通过手动计算,如使用 Object.keys (obj).length 来获取对象中属性的数量,相对而言更麻烦一些。 - 原型链污染风险:
Map:Map 实例是纯净的键值存储,不存在原型链污染的风险,它与原型链完全隔离,不会受到原型对象属性的影响。
Object:对象可能会继承原型链上的属性,存在原型链污染的风险。如果不小心修改了原型对象的属性,可能会影响到所有继承该原型的对象,对于缓存组件来说,这可能导致意外的行为。 - 性能表现:
Map:在频繁增删键值对的场景下,Map 的表现更好,因为其内部结构针对这种操作进行了优化。例如,当组件频繁地被添加到缓存或从缓存中移除时,Map 能提供更高效的性能。
Object:在频繁访问已知属性的场景下,Object 表现更快。但如果是大量的增删操作,Object 的性能会较差,因为它没有针对这种情况做特殊优化。 - 迭代方式:
Map:直接支持 for...of 遍历,并且提供了 entries ()、keys ()、values () 等迭代器方法,方便进行各种形式的迭代操作。
Object:需要先通过 Object.keys ()、Object.values () 等方法将对象的键或值转换为数组,然后才能进行迭代,操作步骤相对较多。
2、DOM处理:
该组件实例对应的整个 DOM 树会被从真实的文档流 (DOM tree) 中完全移除 (detached) 。这就是为什么在页面检查器里看不到它的 DOM 了。
当这个组件再次被激活时,keep-alive 会从缓存中找到这个实例,直接复用这个组件实例(保留所有状态),并将它对应的 DOM 树重新插入 (attached) 到文档流中。
如何动态控制组件缓存
方案1:绑定动态include/exclude(响应式变量)
<keep-alive :include="cachedComponents">
方案2:通过key强制重新渲染(改变key会销毁旧实例)
<component :is="currentComponent" :key="componentKey">
keep-alive如何结合路由使用
搭配router-view
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
路由配置:通过meta字段标记需缓存的页面
{ path: '/home', component: Home, meta: { keepAlive: true } }
缓存组件如何更新数据
onActivated中刷新数据
onActivated(() => {
fetchData(); // 重新请求数据
});
max属性的作用及淘汰策略
作用:避免内存无限增长,限制最大缓存实例
淘汰策略:LRU(最近最少使用),优先移除最久未被访问的实例
注意事项
组件必须设置name选项:否则include/exclude无法匹配
避免内存泄漏:及时清理不需要缓存的组件(如通过max或动态include)
SSR不兼容:keep-alive仅在客户端渲染中生效
缓存组件的状态保留:表单内容等会被保留,需手动重置或通过key强制更新
实战实例
<template>
<button @click="toggleComponent">切换组件</button>
<keep-alive :include="cachedComponents" :max="3">
<component :is="currentComponent" :key="currentComponent" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const currentComponent = ref('ComponentA');
const cachedComponents = ref(['ComponentA', 'ComponentB']);
const toggleComponent = () => {
currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
};
</script>