Vue 3 KeepAlive 指南:路由缓存、状态保留与内存控制

前端工程(更新于 2026年7月15日)

<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
    }
  }
]

如果使用 includeexclude,组件名要稳定,因为 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 的价值在于状态保留,不是自动性能魔法。

本站提供浏览器本地工具,免注册即可试用 →

#Vue3 KeepAlive#组件缓存#路由缓存#性能优化#include exclude#responsive component caching#layout template cache#2026#前端工程