Vue keep-alive 实战避坑:include/exclude + 路由 meta 标记,中后台路由缓存精准可控|状态管理与路由规范篇

【Vue keep-alive】+【中后台路由缓存】:从include/exclude控制到路由meta标记,彻底搞懂页面缓存可控方案,避开组件无name、层级错误等高频坑!

📑 文章目录

  • [一、先搞清楚:为什么要用 keep-alive?](#一、先搞清楚:为什么要用 keep-alive?)
  • [二、基础用法:最简单的 keep-alive](#二、基础用法:最简单的 keep-alive)
    • [2.1 在路由出口处包一层](#2.1 在路由出口处包一层)
  • [三、include / exclude:精确控制「谁缓存、谁不缓存」](#三、include / exclude:精确控制「谁缓存、谁不缓存」)
    • [3.1 参数说明](#3.1 参数说明)
    • [3.2 用 include 做白名单](#3.2 用 include 做白名单)
    • [3.3 用 exclude 做黑名单](#3.3 用 exclude 做黑名单)
    • [3.4 关键点:组件必须有 name](#3.4 关键点:组件必须有 name)
    • [3.5 常见坑:name 要和 include/exclude 里的完全一致](#3.5 常见坑:name 要和 include/exclude 里的完全一致)
  • [四、路由 meta 标记:可维护性更好的方式](#四、路由 meta 标记:可维护性更好的方式)
    • [4.1 路由配置](#4.1 路由配置)
    • [4.2 根据 meta 动态计算 include](#4.2 根据 meta 动态计算 include)
    • [4.3 Vue 2 中的写法](#4.3 Vue 2 中的写法)
  • [五、更完整的实战结构:多级路由 + keep-alive](#五、更完整的实战结构:多级路由 + keep-alive)
    • [5.1 目录结构示例](#5.1 目录结构示例)
    • [5.2 路由配置](#5.2 路由配置)
    • [5.3 MainLayout.vue 中的 keep-alive](#5.3 MainLayout.vue 中的 keep-alive)
    • [5.4 子组件必须定义 name](#5.4 子组件必须定义 name)
  • [六、生命周期:activated 与 deactivated](#六、生命周期:activated 与 deactivated)
    • [6.1 典型用法:刷新列表、恢复滚动](#6.1 典型用法:刷新列表、恢复滚动)
  • 七、常见问题与避坑
  • 八、推荐规范小结
  • 九、小结
  • [🔍 系列模块导航](#🔍 系列模块导航)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、先搞清楚:为什么要用 keep-alive?

日常写 Vue 项目时,从列表进详情再返回,页面会重新渲染,数据和滚动位置都会丢失,用户体验很差。

keep-alive 的作用就是:把匹配到的组件缓存在内存里,切换路由时不会销毁,而是保持上一次的状态。再返回时,数据、滚动条、表单输入都会保留。

  • 适合做缓存的:列表页、搜索结果、表单(多步骤)、数据统计/图表等需要保留状态的页面。
  • 不适合缓存的:登录页、404、纯静态说明页等。

[⬆ 返回目录](#⬆ 返回目录)


二、基础用法:最简单的 keep-alive

2.1 在路由出口处包一层

html 复制代码
<!-- App.vue 或根布局组件 -->
<template>
  <div id="app">
    <router-view v-slot="{ Component }">
      <keep-alive>
        <component :is="Component" />
      </keep-alive>
    </router-view>
  </div>
</template>

这样写会缓存所有路由组件,简单但不可控,一般不推荐。

[⬆ 返回目录](#⬆ 返回目录)


三、include / exclude:精确控制「谁缓存、谁不缓存」

3.1 参数说明

参数 类型 说明
include String RegExp
exclude String RegExp
max Number 最多缓存多少个组件,超出按 LRU 策略淘汰

这里的「名称」指的是组件的 name 选项 ,不是路由的 pathmeta

[⬆ 返回目录](#⬆ 返回目录)

3.2 用 include 做白名单

html 复制代码
<template>
  <router-view v-slot="{ Component }">
    <keep-alive :include="['ProductList', 'OrderList', 'Dashboard']">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

只有 nameProductListOrderListDashboard 的组件会被缓存,其余不缓存。

[⬆ 返回目录](#⬆ 返回目录)

3.3 用 exclude 做黑名单

html 复制代码
<template>
  <router-view v-slot="{ Component }">
    <keep-alive exclude="LoginPage,NotFound">
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

nameLoginPageNotFound 的组件不会被缓存。

[⬆ 返回目录](#⬆ 返回目录)

3.4 关键点:组件必须有 name

html 复制代码
<script setup>
defineOptions({
  name: 'ProductList'  // 必须!否则 include/exclude 无法匹配
})
</script>

<template>
  <div>商品列表页</div>
</template>

如果组件没有 nameinclude / exclude 对它是无效的。

[⬆ 返回目录](#⬆ 返回目录)

3.5 常见坑:name 要和 include/exclude 里的完全一致

js 复制代码
// ❌ 错误:路由配置里的 component 没有 name
{
  path: '/product/list',
  component: () => import('@/views/ProductList.vue')
}
// ProductList.vue 里必须写 defineOptions({ name: 'ProductList' })

// ✅ 正确:组件 name 和 include 数组里的字符串完全一致
// keep-alive :include="['ProductList']"
// ProductList.vue: defineOptions({ name: 'ProductList' })

[⬆ 返回目录](#⬆ 返回目录)


四、路由 meta 标记:可维护性更好的方式

通过路由的 meta 标记「是否需要缓存」,比在 keep-alive 里硬编码 include 数组更清晰,也更易维护。

4.1 路由配置

js 复制代码
// router/index.js
const routes = [
  {
    path: '/product/list',
    name: 'ProductList',
    component: () => import('@/views/ProductList.vue'),
    meta: { keepAlive: true }   // 标记为需要缓存
  },
  {
    path: '/product/detail/:id',
    name: 'ProductDetail',
    component: () => import('@/views/ProductDetail.vue'),
    meta: { keepAlive: false }  // 详情页一般不缓存
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { keepAlive: false }
  }
]

[⬆ 返回目录](#⬆ 返回目录)

4.2 根据 meta 动态计算 include

html 复制代码
<!-- App.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <keep-alive :include="cachedViews">
      <component :is="Component" :key="route.path" />
    </keep-alive>
  </router-view>
</template>

<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

// 从路由配置中筛选出 meta.keepAlive === true 的组件名
const cachedViews = computed(() => {
  return router.getRoutes()
    .filter(route => route.meta?.keepAlive && route.name)
    .map(route => route.name)
})
</script>

这样,只要在路由里改 meta.keepAlive,就能控制哪些页面被缓存,不用再去改 keep-alive 的配置。

[⬆ 返回目录](#⬆ 返回目录)

4.3 Vue 2 中的写法

js 复制代码
computed: {
  cachedViews() {
    return this.$router.options.routes
      .filter(route => route.meta?.keepAlive && route.name)
      .map(route => route.name)
  }
}

[⬆ 返回目录](#⬆ 返回目录)


五、更完整的实战结构:多级路由 + keep-alive

很多项目会区分「有布局的页面」和「无布局的页面」,布局里再包一层 router-view,需要把 keep-alive 放在正确的层级。

5.1 目录结构示例

复制代码
views/
├── layout/
│   └── MainLayout.vue    # 主布局,包含侧边栏 + 内容区
├── product/
│   ├── ProductList.vue   # 商品列表(缓存)
│   └── ProductDetail.vue # 商品详情(不缓存)
└── Login.vue             # 登录(不缓存)

[⬆ 返回目录](#⬆ 返回目录)

5.2 路由配置

js 复制代码
const routes = [
  {
    path: '/login',
    component: () => import('@/views/Login.vue'),
    meta: { keepAlive: false }
  },
  {
    path: '/',
    component: () => import('@/views/layout/MainLayout.vue'),
    children: [
      {
        path: 'product/list',
        name: 'ProductList',
        component: () => import('@/views/product/ProductList.vue'),
        meta: { keepAlive: true, title: '商品列表' }
      },
      {
        path: 'product/detail/:id',
        name: 'ProductDetail',
        component: () => import('@/views/product/ProductDetail.vue'),
        meta: { keepAlive: false, title: '商品详情' }
      }
    ]
  }
]

[⬆ 返回目录](#⬆ 返回目录)

5.3 MainLayout.vue 中的 keep-alive

html 复制代码
<!-- views/layout/MainLayout.vue -->
<template>
  <div class="main-layout">
    <aside class="sidebar">...</aside>
    <main class="content">
      <router-view v-slot="{ Component, route }">
        <keep-alive :include="cachedViews">
          <component :is="Component" :key="route.path" />
        </keep-alive>
      </router-view>
    </main>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const cachedViews = computed(() => {
  // 只处理当前 layout 下的 children,避免把 Login 等算进来
  const layout = router.getRoutes().find(r => r.path === '/')
  if (!layout?.children) return []
  return layout.children
    .filter(route => route.meta?.keepAlive && route.name)
    .map(route => route.name)
})
</script>

[⬆ 返回目录](#⬆ 返回目录)

5.4 子组件必须定义 name

html 复制代码
<!-- ProductList.vue -->
<script setup>
defineOptions({ name: 'ProductList' })
</script>

[⬆ 返回目录](#⬆ 返回目录)


六、生命周期:activated 与 deactivated

keep-alive 缓存的组件,不会反复触发 mounted / unmounted,而是使用:

  • activated:组件被激活(从别的路由切回来时)
  • deactivated:组件被停用(切走时)

6.1 典型用法:刷新列表、恢复滚动

html 复制代码
<script setup>
import { ref, onActivated } from 'vue'
import { useRoute } from 'vue-router'

const list = ref([])
const scrollTop = ref(0)
const route = useRoute()

// 首次进入或从详情返回,都可以在这里刷新
onActivated(() => {
  loadList()
  nextTick(() => {
    listRef.value?.scrollTo(0, scrollTop.value)
  })
})

// 离开时保存滚动位置
onDeactivated(() => {
  scrollTop.value = listRef.value?.scrollTop ?? 0
})

async function loadList() {
  const res = await fetchList(route.query)
  list.value = res.data
}
</script>

[⬆ 返回目录](#⬆ 返回目录)


七、常见问题与避坑

7.1 缓存了不该缓存的页面

现象 :登录后退出,再进登录页,还能看到上次的账号。
原因 :登录页被 keep-alive 缓存了。
处理 :在 excludemeta.keepAlive 里排除登录页,并确保组件有正确的 name

7.2 以为缓存了,实际没缓存

现象:列表页返回后数据丢失。

排查

  1. 组件是否定义了 name
  2. name 是否和 include / cachedViews 中完全一致;
  3. keep-alive 是否包在了正确的 router-view 外层(多级路由时)。

7.3 多 Tab 页只缓存一个

现象 :有多个 Tab,切换后只保留最后一个的状态。
原因 :多个 Tab 可能共用同一个路由 path,没有用 key 区分实例。
处理 :给 component 加不同的 key,例如:

html 复制代码
<component :is="Component" :key="route.fullPath" />

这样同一路由的不同参数会当成不同组件实例进行缓存。

7.4 缓存的页面需要「按需刷新」

可以在 meta 里加标识,或在 activated 里根据条件决定是否重新请求:

js 复制代码
// 方式1:meta 里标记
meta: { keepAlive: true, refreshOnActivate: true }

// 方式2:activated 里判断
onActivated(() => {
  if (route.query.refresh === '1') {
    loadList()
  }
})

[⬆ 返回目录](#⬆ 返回目录)


八、推荐规范小结

场景 建议做法
缓存策略 用路由 meta.keepAlive 控制,避免在模板里硬编码
组件名 每个页面组件都定义 name,且和路由 name 保持一致
keep-alive 位置 放在实际渲染页面的 router-view 外层(多级路由时注意层级)
列表页 activated 中按需刷新,用 deactivated 保存滚动位置
登录 / 404 等 一律 meta.keepAlive: false 或加入 exclude

[⬆ 返回目录](#⬆ 返回目录)


九、小结

keep-alive 的核心就是三件事:

  1. include / exclude :通过组件 name 决定缓存范围;
  2. meta.keepAlive:用路由配置统一管理,方便维护;
  3. activated / deactivated:在缓存组件里做刷新和状态恢复。

记住:组件必须写 name,否则 include / exclude 不会生效。结合路由 meta 和生命周期,就可以把缓存控制得清晰、可控。

[⬆ 返回目录](#⬆ 返回目录)

🔍 系列模块导航

📝 状态管理与路由规范

一、《Vue3 Pinia 状态管理规范:状态拆分、Actions 写法、持久化实战,避坑状态污染|状态管理与路由规范篇》
二、《Vue3 Pinia 状态管理规范:何时用 Pinia 何时用本地状态|状态管理与路由规范篇》
三、《Vue Router 实战规范:path/name/meta 配置 + 动态 / 嵌套路由,统一团队标准|状态管理与路由规范篇》
四、《Vue3 + Vue Router + Pinia 路由守卫规范:beforeEach 应做 / 不应做,避死循环、防重复请求|状态管理与路由规范篇》

五、《Vue keep-alive 实战避坑:include/exclude + 路由 meta 标记,中后台路由缓存精准可控|状态管理与路由规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。

更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
Highcharts.js1 小时前
Highcharts React v4 迁移指南(上):核心变更解析与升级收益
前端·javascript·react.js·react·数据可视化·highcharts·v4迁移
SuniaWang1 小时前
《Spring AI + 大模型全栈实战》学习手册系列 · 专题八:《RAG 系统安全与权限管理:企业级数据保护方案》
java·前端·人工智能·spring boot·后端·spring·架构
菌菌的快乐生活1 小时前
在 WPS 中设置 “第一章”“第二章” 这类一级编号标题自动跳转至新页面
前端·javascript·wps
南 阳2 小时前
Python从入门到精通day56
开发语言·python
m0_569881472 小时前
C++中的组合模式高级应用
开发语言·c++·算法
hh随便起个名2 小时前
useRef和useState对比
前端·javascript·react
m0_730115112 小时前
高性能计算负载均衡
开发语言·c++·算法
Hello_Embed2 小时前
LVGL 入门(十五):接口优化
前端·笔记·stm32·单片机·嵌入式
孞㐑¥2 小时前
算法—记忆化搜索
开发语言·c++·经验分享·笔记·算法
xushichao19892 小时前
代码覆盖率工具实战
开发语言·c++·算法