给我五分钟,我把 KeepAlive 的用法和原理跟你说明白
原文主要介绍 Vue 2 的
<keep-alive>、路由页面缓存和 LRU 原理。本文保留原有示例和源码学习路线,去掉抓取产生的代码标记,补充 Vue 3.5、Vue Router 4/5、<router-view>插槽、组件名称匹配和缓存失效场景。
一、KeepAlive 解决什么问题
默认情况下,动态组件切换会卸载当前组件实例,再创建下一个组件实例:
<component :is="activeComponent" />
如果组件中有输入框、分页、滚动位置或临时筛选条件,离开后再回来通常会重新初始化。Vue 内置的 <KeepAlive> 可以缓存动态组件实例:组件从页面移除时进入 deactivated 状态,而不是立即卸载;再次显示时从缓存中 activated。
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
它缓存的是组件实例及其子树的运行时状态,不是把页面 HTML 永久保存下来,也不是浏览器 HTTP 缓存。

KeepAlive 的三个常用属性
| 属性 | 作用 |
|---|---|
include | 只缓存名称匹配的组件 |
exclude | 排除名称匹配的组件 |
max | 限制缓存实例数量,超出后淘汰最久未访问的实例 |
include 和 exclude 支持逗号分隔字符串、正则表达式和数组:
<KeepAlive include="Home,UserList">
<component :is="activeComponent" />
</KeepAlive>
<KeepAlive :include="['Home', 'UserList']">
<component :is="activeComponent" />
</KeepAlive>
<KeepAlive :exclude="/Edit/">
<component :is="activeComponent" />
</KeepAlive>
匹配的是组件的 name,不是路由配置里的 name 字段。需要按名称筛选时,应确保组件拥有正确的 name:
<script setup>
defineOptions({ name: 'Home' })
</script>
Vue 3.2.34 及以后,使用 <script setup> 的单文件组件通常会根据文件名自动推断组件名;为了避免重命名文件、打包工具或异步组件造成歧义,重要的缓存组件仍可以显式声明 name。
max 和 LRU
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
当缓存数量即将超过 max 时,KeepAlive 会淘汰最近最少使用(Least Recently Used,LRU)的缓存实例。被淘汰的实例会真正卸载,相关的 onUnmounted/unmounted 清理逻辑会执行。
max 不是“最多渲染 10 个组件”,而是“最多保留 10 个已缓存的组件实例”。缓存项数量、组件子树大小和页面数据都会影响内存占用。
二、完整的动态组件示例
Home.vue
<script setup>
import { ref } from 'vue'
defineOptions({ name: 'Home' })
const keyword = ref('')
</script>
<template>
<section>
<h1>首页</h1>
<input v-model="keyword" placeholder="输入内容后切换页面" />
<p>当前输入:{{ keyword }}</p>
</section>
</template>
About.vue
<script setup>
defineOptions({ name: 'About' })
</script>
<template>
<section>
<h1>关于页面</h1>
</section>
</template>
App.vue
<script setup>
import { ref } from 'vue'
import Home from './Home.vue'
import About from './About.vue'
const activeComponent = ref(Home)
</script>
<template>
<nav>
<button @click="activeComponent = Home">Home</button>
<button @click="activeComponent = About">About</button>
</nav>
<KeepAlive include="Home">
<component :is="activeComponent" />
</KeepAlive>
</template>
只有 Home 会被缓存。切换到 About 时,Home 进入 deactivated;再次切回时,输入框仍然保留原来的值。若移除 include,两个组件都会进入缓存。
Vue 2 的等价写法:
<keep-alive include="Home">
<component :is="activeComponent" />
</keep-alive>
组件需要使用 export default { name: 'Home' } 声明名称。Vue 2 的组件名匹配规则和 Vue 3 基本一致,但 Vue 3 的 <script setup> 自动推断能力不能直接套用到 Vue 2 项目。
三、和 Vue Router 一起缓存页面
1. Vue 2 / Router 3 的常见写法
历史项目中,直接包裹 <router-view> 是常见方案:
<template>
<div id="app">
<keep-alive include="Home">
<router-view />
</keep-alive>
</div>
</template>
路由组件:
// router.js,Vue 2 + vue-router 3
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/about',
name: 'about',
component: () => import('./views/About.vue'),
},
],
})
include="Home" 匹配的是 Home.vue 的组件 name,不是上面路由记录的 name: 'home'。两者大小写也可能不同,排查缓存不生效时应先检查这一点。
2. Vue 3 / Router 4–5 的推荐写法
Vue Router 4/5 提供了 <router-view> 插槽。KeepAlive 应该放在插槽内部,缓存真正的路由组件,而不是缓存 RouterView 组件本身:
<template>
<RouterView v-slot="{ Component }">
<KeepAlive include="Home">
<component :is="Component" />
</KeepAlive>
</RouterView>
</template>
如果还要配合过渡动画:
<template>
<RouterView v-slot="{ Component }">
<Transition mode="out-in">
<KeepAlive>
<component :is="Component" />
</KeepAlive>
</Transition>
</RouterView>
</template>
Transition 和 KeepAlive 的嵌套顺序应根据期望的进入/离开效果验证,不要只复制某个版本的写法。
3. 按路由 meta 决定是否缓存
可以在路由记录中保存业务配置:
// router/index.ts,Vue Router 4/5
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/Home.vue'),
meta: { keepAlive: true },
},
{
path: '/about',
name: 'about',
component: () => import('../views/About.vue'),
meta: { keepAlive: false },
},
]
模板可以按当前路由分支:
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive>
<component
v-if="route.meta.keepAlive"
:is="Component"
:key="route.meta.cacheKey || route.name"
/>
</KeepAlive>
<component
v-if="!route.meta.keepAlive"
:is="Component"
:key="route.fullPath"
/>
</RouterView>
</template>
实际项目中要保证同一次渲染只有一个 route component 作为 KeepAlive 的子节点,并根据参数路由决定 key 策略。beforeRouteLeave 中给 to.meta.keepAlive 赋值的旧示例容易污染路由记录,也不能覆盖所有返回路径,通常不如在路由配置中明确声明。

4. 参数路由的 key
/users/1 和 /users/2 可能复用同一个 User 组件实例,因为路由组件类型没有改变:
<RouterView v-slot="{ Component, route }">
<KeepAlive>
<!-- 需要每个参数一份缓存时才使用 fullPath -->
<component :is="Component" :key="route.fullPath" />
</KeepAlive>
</RouterView>
使用 fullPath 会让每个参数组合都成为一个缓存项,可能快速占满缓存。若希望两个用户共用组件实例并监听 route.params.id,可以不加这个 key,并在组件中响应参数变化。
四、缓存组件的生命周期
Options API:
export default {
activated() {
// 首次挂载时也会调用,之后每次从缓存重新插入时调用
this.refreshIfNeeded()
},
deactivated() {
// 从 DOM 移入缓存时调用;组件真正卸载时也会调用
this.stopPolling()
},
}
Composition API:
<script setup>
import { onActivated, onDeactivated, onUnmounted } from 'vue'
let timer
function startPolling() {
timer = window.setInterval(() => {
// 拉取或刷新当前页面需要的数据
}, 30_000)
}
function stopPolling() {
window.clearInterval(timer)
timer = undefined
}
onActivated(startPolling)
onDeactivated(stopPolling)
onUnmounted(stopPolling)
</script>
需要注意:
activated/onActivated首次挂载时也会执行;deactivated不是unmounted,组件实例、DOM 状态和本地响应式数据仍在缓存中;- KeepAlive 不应被理解为自动暂停所有定时器、WebSocket、第三方订阅或业务 watcher;这些外部资源需要在生命周期中自行管理;
- 组件最终因 KeepAlive 淘汰或父树卸载时,仍会执行卸载清理。

五、KeepAlive 的实现原理
1. 它不是普通的 DOM 容器
KeepAlive 是 Vue 的内置组件。它不会额外渲染一个 <div>,而是接收默认插槽中的动态组件 vnode,并告诉 renderer:
- 这个 vnode 可以进入缓存;
- 再次出现时复用原来的组件实例;
- 离开时执行 deactivate,而不是普通 unmount;
- 需要淘汰时才执行真正的卸载。
Vue 2 中它是抽象组件,核心源码位于 src/core/components/keep-alive.js;Vue 3 中实现位于 runtime-core 的 KeepAlive 组件,并与 renderer 的 activate/deactivate 流程协作。
2. Vue 2 源码的核心模型
下面是从 Vue 2 源码抽取的教学版伪代码,省略了异步组件、transition 和销毁细节:
const cache = Object.create(null)
const keys = []
function render(vnode, include, exclude, max) {
const name = getComponentName(vnode)
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const key = vnode.key == null
? `${vnode.componentOptions.Ctor.cid}::${vnode.componentOptions.tag || ''}`
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
if (max && keys.length > Number(max)) {
const oldestKey = keys.shift()
pruneCacheEntry(cache, oldestKey)
}
}
vnode.data.keepAlive = true
return vnode
}
真实源码还会在 include/exclude 改变时清理不符合条件的缓存,并在组件销毁时遍历缓存销毁实例。上面 cache/keys 的关系体现了原文所说的 LRU:命中缓存时把 key 移到末尾,淘汰时移除最前面的 key。
3. Vue 3 的实现重点
Vue 3 仍然是同一套思想,但运行时 vnode 有 KeepAlive 专用标记,renderer 会把组件子树移动到内部存储容器:
首次出现:mount component
切换离开:deactivate,保留 instance 和 vnode
再次出现:activate,把已有 DOM 插回目标位置
超过 max:prune cache,unmount 被淘汰实例
实现中会使用缓存表和 key 集合记录组件,并根据 include、exclude、max 处理缓存。不同小版本的内部字段和优化可能变化,不应在业务代码中直接访问 KeepAlive 的内部 cache。

六、常见问题与排查顺序
1. 为什么写了 KeepAlive 仍然不缓存
按以下顺序检查:
- KeepAlive 的子节点是否真的是动态组件或 RouterView slot 中的路由组件;
include/exclude是否匹配组件name,大小写是否正确;- 是否给每次切换都设置了变化的 key,导致每次都是新缓存项;
- 组件是否被
v-if完全移出 KeepAlive 外部; max是否太小,导致组件刚离开就被淘汰;- 是否误以为 KeepAlive 会缓存 Pinia、接口数据或浏览器滚动位置——这些需要单独处理。
2. 什么时候不应该使用
不适合无限缓存大型页面、包含大量图表/编辑器的实例,或每次进入都必须重新获取权限与数据的页面。可以使用 max、手动刷新策略、分页、状态管理或服务端缓存控制内存与数据新鲜度。
3. 如何清除缓存
KeepAlive 没有一个推荐给业务代码直接调用的统一 clearCache() 公共 API。常用方式是:
- 调整
include,让目标组件不再匹配; - 修改合理的 vnode key,使其创建新实例;
- 通过条件渲染让 KeepAlive 整体卸载再重新挂载;
- 将需要持久化的数据移到 Pinia 等状态层,而不是依赖组件缓存。
这些方式会影响组件生命周期,应结合业务确认。
总结
<KeepAlive>缓存的是动态组件实例,不是普通 HTML 或接口响应;include/exclude按组件name匹配,max使用 LRU 思路淘汰旧实例;- 缓存组件离开时触发 deactivated,再次出现时触发 activated;
- 路由缓存中,Vue Router 4/5 推荐把 KeepAlive 放到
<router-view>插槽内部; - Vue 2 可以直接包裹
<router-view>,但应区分 Router 3 与 Router 4/5 的写法; - 参数路由的 key 决定是复用同一实例,还是为每个参数创建独立缓存;
- 缓存不是越多越好,应处理定时器、WebSocket、内存和数据刷新问题。
参考资料
- Vue 3:KeepAlive
- Vue 3:内置组件 API
- Vue 2:Keep-Alive
- Vue Router:RouterView 插槽与 KeepAlive
- Vue 3 KeepAlive 源码
- Vue 2 KeepAlive 源码
原文作者:白瓷梅子汤。原文关于动态组件缓存、include、exclude、max、activated/deactivated、路由缓存和 LRU 的主线予以保留;抓取的代码标记、Vue 2/Router 3 写法与 Vue 3 混淆、错误的组件名说明以及不严谨的缓存清理表述已整理或修正。