Vue 3 KeepAlive 指南:路由缓存、状态保留与内存控制
<KeepAlive> 是 Vue 内置的组件缓存能力,用来缓存暂时离开的组件实例。用户从一个页面离开后再返回时,筛选条件、表单草稿、滚动位置和局部 UI 状态可以保留下来。
KeepAlive 也很容易被误用。如果所有路由都缓存,内存会增长;如果缓存数据从不刷新,用户会看到旧信息;如果组件名和路由 key 没设计好,缓存行为可能和预期不同。
参考文档:
什么时候值得使用 KeepAlive
适合使用 KeepAlive 的页面,通常是“保留局部状态能明显改善体验”的页面:
- 带筛选、搜索、标签页和滚动位置的列表页。
- 多步骤表单,用户可能中途查看其他页面再回来。
- 本地渲染状态较重的仪表盘。
- 后台系统里的多标签页面,切换时不应重置。
这些页面不适合缓存:
- 重新创建成本很低的页面。
- 每次进入都必须显示最新数据的页面。
- 离开后应清空敏感信息的页面。
- 持有定时器、订阅或大对象,且难以暂停的组件。
默认策略应该是“选择性缓存”,而不是缓存所有路由。
基础路由缓存写法
Vue Router 可以通过 RouterView 的 slot 暴露当前路由组件,这是包裹 KeepAlive 的常见位置。
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="cachedComponentNames" :max="8">
<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>
<script setup lang="ts">
const cachedComponentNames = ["UserList", "OrderList", "ReportDashboard"]
</script>
把缓存决策写到路由 meta 中,更容易维护。
// router.ts
import type { RouteRecordRaw } from "vue-router"
export const routes: RouteRecordRaw[] = [
{
path: "/users",
name: "Users",
component: () => import("./views/UserList.vue"),
meta: {
keepAlive: true,
cacheKey: "users-list"
}
},
{
path: "/users/:id",
name: "UserDetail",
component: () => import("./views/UserDetail.vue"),
meta: {
keepAlive: false
}
}
]
如果使用 include 或 exclude,组件名要稳定,因为 Vue 会按组件名匹配缓存。
<!-- UserList.vue -->
<script setup lang="ts">
defineOptions({
name: "UserList"
})
</script>
刷新数据但保留 UI 状态
最常见的问题是缓存数据过期。用户返回缓存页面时,列表数据可能已经不是最新。
可以在 onActivated 中判断是否需要刷新。不要每次激活都无脑重载,否则缓存带来的状态保留价值会下降。
<script setup lang="ts">
import { onActivated, onDeactivated, ref } from "vue"
const users = ref<User[]>([])
const loading = ref(false)
const lastLoadedAt = ref(0)
const STALE_AFTER_MS = 60_000
async function loadUsers() {
loading.value = true
try {
users.value = await fetchUsers()
lastLoadedAt.value = Date.now()
} finally {
loading.value = false
}
}
onActivated(() => {
const isStale = Date.now() - lastLoadedAt.value > STALE_AFTER_MS
if (!users.value.length || isStale) {
void loadUsers()
}
})
onDeactivated(() => {
// 暂停页面隐藏后不应继续运行的任务。
})
</script>
这样既能保留筛选、滚动等局部状态,也能在数据过期时刷新。
保留滚动位置
KeepAlive 会保留组件实例,但滚动位置仍可能受布局、过渡动画或容器滚动影响。
如果页面使用内部滚动容器,可以手动保存和恢复:
<template>
<main ref="scrollEl" class="page-scroll">
<!-- list content -->
</main>
</template>
<script setup lang="ts">
import { nextTick, onActivated, onDeactivated, ref } from "vue"
const scrollEl = ref<HTMLElement | null>(null)
let savedTop = 0
onDeactivated(() => {
savedTop = scrollEl.value?.scrollTop ?? 0
})
onActivated(async () => {
await nextTick()
if (scrollEl.value) {
scrollEl.value.scrollTop = savedTop
}
})
</script>
如果依赖浏览器滚动恢复,要同时测试直接访问、后退按钮和代码跳转。
控制缓存数量
max 属性可以限制 KeepAlive 保留的组件实例数量。超过限制后,Vue 会裁剪较旧的缓存实例。
<KeepAlive :max="8">
<component :is="Component" />
</KeepAlive>
缓存数量应按真实使用路径设置。小型后台可能只需要几个列表缓存;多标签系统可能需要更多。不要把 max 设置得很大来掩盖状态管理问题。
避免内存泄漏
被缓存的组件是停用,不是卸载。因此有些清理要放在 onDeactivated,最终清理仍要放在 onUnmounted。
需要暂停或移除:
- 定时器。
- 页面隐藏后不该继续运行的 WebSocket 或 SSE 监听。
- ResizeObserver 和 IntersectionObserver。
- 全局事件监听。
- 可以重新生成的大数组、Map、Blob 等临时对象。
import { onActivated, onDeactivated, onUnmounted } from "vue"
let timer: number | undefined
function startPolling() {
timer = window.setInterval(refreshStatus, 15_000)
}
function stopPolling() {
if (timer) {
window.clearInterval(timer)
timer = undefined
}
}
onActivated(startPolling)
onDeactivated(stopPolling)
onUnmounted(stopPolling)
如果反复切换页面后内存仍然增长,应使用浏览器开发者工具做快照分析,不要靠猜。
缓存 Key 与动态路由
动态路由要认真设计 key。如果多个详情页使用同一个组件名和同一个 key,可能会共享同一个缓存实例。
常见写法:
<!-- 用户列表共享一个缓存实例 -->
<component :is="Component" :key="route.name" />
<!-- 每个参数对应独立缓存 -->
<component :is="Component" :key="route.fullPath" />
<!-- 使用自定义 key 控制 -->
<component :is="Component" :key="route.meta.cacheKey || route.fullPath" />
列表页不要随便使用 fullPath。如果查询参数组合很多,可能创建过多缓存项。列表页通常更适合缓存一个实例,把筛选条件保存在组件或 store 中。
实用规则
KeepAlive 应用于保留有价值的局部状态,不应该被当成通用性能开关。
适合缓存:
- 搜索结果列表。
- 表单草稿。
- 图表初始化成本较高的仪表盘。
- 用户期望快速切换的标签页。
不适合缓存:
- 登录、认证页面。
- 支付或安全敏感页面。
- 每次进入都必须重新获取数据的页面。
- 大量不同 ID 的详情页,除非有明确上限。
发布前检查
上线路由缓存前确认:
- 只有选定路由使用 KeepAlive。
- 缓存组件有稳定名称。
- 动态路由 key 是有意设计的。
max设置合理。- 需要时通过
onActivated刷新数据。 - 定时器、观察器和监听器会在停用时暂停。
- 敏感页面没有被缓存。
- 后退按钮、直接访问和标签切换都测试过。
KeepAlive 的价值在于状态保留,不是自动性能魔法。
本站提供浏览器本地工具,免注册即可试用 →