【Vue keep-alive】+【中后台路由缓存】:从include/exclude控制到路由meta标记,彻底搞懂页面缓存可控方案,避开组件无name、层级错误等高频坑!
📑 文章目录
- 一、先搞清楚:为什么要用 keep-alive?
- 二、基础用法:最简单的 keep-alive
- 2.1 在路由出口处包一层
- 三、include / exclude:精确控制「谁缓存、谁不缓存」
- 3.1 参数说明
- 3.2 用 include 做白名单
- 3.3 用 exclude 做黑名单
- 3.4 关键点:组件必须有 name
- 3.5 常见坑:name 要和 include/exclude 里的完全一致
- 四、路由 meta 标记:可维护性更好的方式
- 4.1 路由配置
- 4.2 根据 meta 动态计算 include
- 4.3 Vue 2 中的写法
- 五、更完整的实战结构:多级路由 + keep-alive
- 5.1 目录结构示例
- 5.2 路由配置
- 5.3 MainLayout.vue 中的 keep-alive
- 5.4 子组件必须定义 name
- 六、生命周期:activated 与 deactivated
- 6.1 典型用法:刷新列表、恢复滚动
- 七、常见问题与避坑
- 八、推荐规范小结
- 九、小结
- 🔍 系列模块导航
同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。
(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)
很多前端开发者都会遇到一个瓶颈:
代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。
想写出干净、优雅、可维护的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验。
这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。
帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。
一、先搞清楚:为什么要用 keep-alive?
日常写 Vue 项目时,从列表进详情再返回,页面会重新渲染,数据和滚动位置都会丢失,用户体验很差。
keep-alive的作用就是:把匹配到的组件缓存在内存里,切换路由时不会销毁,而是保持上一次的状态。再返回时,数据、滚动条、表单输入都会保留。
- 适合做缓存的:列表页、搜索结果、表单(多步骤)、数据统计/图表等需要保留状态的页面。
- 不适合缓存的:登录页、404、纯静态说明页等。
⬆ 返回目录
二、基础用法:最简单的 keep-alive
2.1 在路由出口处包一层
<!-- App.vue 或根布局组件 --><template><divid="app"><router-viewv-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选项,不是路由的path或meta。
⬆ 返回目录
3.2 用 include 做白名单
<template><router-viewv-slot="{ Component }"><keep-alive:include="['ProductList', 'OrderList', 'Dashboard']"><component:is="Component"/></keep-alive></router-view></template>只有name为ProductList、OrderList、Dashboard的组件会被缓存,其余不缓存。
⬆ 返回目录
3.3 用 exclude 做黑名单
<template><router-viewv-slot="{ Component }"><keep-aliveexclude="LoginPage,NotFound"><component:is="Component"/></keep-alive></router-view></template>name为LoginPage、NotFound的组件不会被缓存。
⬆ 返回目录
3.4 关键点:组件必须有 name
<scriptsetup>defineOptions({name:'ProductList'// 必须!否则 include/exclude 无法匹配})</script><template><div>商品列表页</div></template>如果组件没有name,include/exclude对它是无效的。
⬆ 返回目录
3.5 常见坑:name 要和 include/exclude 里的完全一致
// ❌ 错误:路由配置里的 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 路由配置
// router/index.jsconstroutes=[{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
<!-- App.vue --><template><router-viewv-slot="{ Component, route }"><keep-alive:include="cachedViews"><component:is="Component":key="route.path"/></keep-alive></router-view></template><scriptsetup>import{computed}from'vue'import{useRouter}from'vue-router'constrouter=useRouter()// 从路由配置中筛选出 meta.keepAlive === true 的组件名constcachedViews=computed(()=>{returnrouter.getRoutes().filter(route=>route.meta?.keepAlive&&route.name).map(route=>route.name)})</script>这样,只要在路由里改meta.keepAlive,就能控制哪些页面被缓存,不用再去改keep-alive的配置。
⬆ 返回目录
4.3 Vue 2 中的写法
computed:{cachedViews(){returnthis.$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 路由配置
constroutes=[{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
<!-- views/layout/MainLayout.vue --><template><divclass="main-layout"><asideclass="sidebar">...</aside><mainclass="content"><router-viewv-slot="{ Component, route }"><keep-alive:include="cachedViews"><component:is="Component":key="route.path"/></keep-alive></router-view></main></div></template><scriptsetup>import{computed}from'vue'import{useRouter}from'vue-router'constrouter=useRouter()constcachedViews=computed(()=>{// 只处理当前 layout 下的 children,避免把 Login 等算进来constlayout=router.getRoutes().find(r=>r.path==='/')if(!layout?.children)return[]returnlayout.children.filter(route=>route.meta?.keepAlive&&route.name).map(route=>route.name)})</script>⬆ 返回目录
5.4 子组件必须定义 name
<!-- ProductList.vue --><scriptsetup>defineOptions({name:'ProductList'})</script>⬆ 返回目录
六、生命周期:activated 与 deactivated
被keep-alive缓存的组件,不会反复触发mounted/unmounted,而是使用:
- activated:组件被激活(从别的路由切回来时)
- deactivated:组件被停用(切走时)
6.1 典型用法:刷新列表、恢复滚动
<scriptsetup>import{ref,onActivated}from'vue'import{useRoute}from'vue-router'constlist=ref([])constscrollTop=ref(0)constroute=useRoute()// 首次进入或从详情返回,都可以在这里刷新onActivated(()=>{loadList()nextTick(()=>{listRef.value?.scrollTo(0,scrollTop.value)})})// 离开时保存滚动位置onDeactivated(()=>{scrollTop.value=listRef.value?.scrollTop??0})asyncfunctionloadList(){constres=awaitfetchList(route.query)list.value=res.data}</script>⬆ 返回目录
七、常见问题与避坑
7.1 缓存了不该缓存的页面
现象:登录后退出,再进登录页,还能看到上次的账号。
原因:登录页被keep-alive缓存了。
处理:在exclude或meta.keepAlive里排除登录页,并确保组件有正确的name。
7.2 以为缓存了,实际没缓存
现象:列表页返回后数据丢失。
排查:
- 组件是否定义了
name; name是否和include/cachedViews中完全一致;keep-alive是否包在了正确的router-view外层(多级路由时)。
7.3 多 Tab 页只缓存一个
现象:有多个 Tab,切换后只保留最后一个的状态。
原因:多个 Tab 可能共用同一个路由path,没有用key区分实例。
处理:给component加不同的key,例如:
<component:is="Component":key="route.fullPath"/>这样同一路由的不同参数会当成不同组件实例进行缓存。
7.4 缓存的页面需要「按需刷新」
可以在meta里加标识,或在activated里根据条件决定是否重新请求:
// 方式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的核心就是三件事:
- include / exclude:通过组件
name决定缓存范围; - meta.keepAlive:用路由配置统一管理,方便维护;
- 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,与你一起写规范、写优质代码,我们下篇干货见~