第三十六节:keep-alive 页面缓存(Vue3 + 后台管理系统)
本节目标:
实现路由页面缓存 ,切换标签页回来保留表单 / 表格查询条件、页码;离开页面不销毁组件;不需要缓存的页面可以排除。
对应咱们项目的 TagsView 标签页,就是搭配 keep-alive 使用,属于当前主线内容,现在直接讲。
原理
keep-alive 是 Vue 内置组件,缓存组件实例,不会销毁。
- 打开页面:
onMounted只执行一次 - 切走再切回来:不会重新请求接口,直接复用之前 DOM 和数据
- 生命周期:
onActivated(激活,每次进入) /onDeactivated(失活,切走)
步骤 1:修改布局 src/layout/index.vue
<template>
<div class="layout-container">
<Sidebar />
<div class="main-container">
<AppHeader />
<TagsView />
<!-- 核心:keep-alive + router-view -->
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useTagsViewStore } from '@/stores/tagsView'
const tagsViewStore = useTagsViewStore()
// 从pinia取出需要缓存的页面名称数组
const cachedViews = ref(tagsViewStore.cachedViews)
</script>
步骤 2:改造 src/stores/tagsView.js
新增 cachedViews 数组,用来存放组件 name ,keep-alive 的 include 靠组件 name 匹配缓存!
⚠️重点坑:keep-alive 匹配的是组件选项 name,不是路由 path!
import { defineStore } from 'pinia'
export const useTagsViewStore = defineStore('tagsView', {
state: () => ({
visitedViews: [], // 标签页列表(展示用)
cachedViews: [] // keep-alive缓存名称数组
}),
actions: {
// 添加标签,同时加入缓存
addView(view) {
if (this.visitedViews.some(v => v.path === view.path)) return
this.visitedViews.push({
path: view.path,
title: view.meta?.title || 'no-name'
})
// 有组件name,才加入缓存
if (view.name && !this.cachedViews.includes(view.name)) {
this.cachedViews.push(view.name)
}
},
// 删除标签,同时移除缓存
delView(view) {
const index = this.visitedViews.findIndex(v => v.path === view.path)
if (index > -1) {
this.visitedViews.splice(index, 1)
}
// 删除缓存名称
if (view.name) {
const cacheIndex = this.cachedViews.indexOf(view.name)
if (cacheIndex > -1) {
this.cachedViews.splice(cacheIndex, 1)
}
}
// 返回剩下的标签用于页面跳转
return this.visitedViews[this.visitedViews.length - 1]
},
// 关闭其他标签
delOthersViews(view) {
this.visitedViews = this.visitedViews.filter(item => {
return item.path === view.path || item.path === '/dashboard/index'
})
this.cachedViews = this.cachedViews.filter(name => {
return view.name === name || name === 'Dashboard'
})
},
// 关闭全部
delAllViews() {
const dashboard = this.visitedViews.find(v => v.path === '/dashboard/index')
this.visitedViews = dashboard ? [dashboard] : []
this.cachedViews = dashboard ? ['Dashboard'] : []
}
}
})
步骤 3:给业务页面添加 name 属性(必须!)
示例 src/views/system/user/index.vue
<script setup>
// ✅ 定义组件name,keep-alive靠这个识别
defineOptions({
name: 'UserList'
})
// 剩下你的页面逻辑...
</script>
工作台页面 dashboard/index.vue
<script setup>
defineOptions({
name: 'Dashboard'
})
</script>
步骤 4:生命周期变化(缓存页面要用)
// 首次进入执行一次
onMounted(() => {
console.log('组件挂载')
})
// ✅ 每次切换标签回来都会执行(缓存页面推荐在这里请求数据)
onActivated(() => {
console.log('页面激活')
// getList()
})
// ✅ 页面切走失活
onDeactivated(() => {
console.log('页面缓存,暂时隐藏')
})
测试清单
- 重启项目,进入用户管理页面,设置查询条件、页码
- 切到别的标签,再切回用户管理
✅ 查询条件、页码保留,不会重新刷新页面 - 关闭标签,
cachedViews自动移除组件 name,再次打开页面会重新加载 - 关闭其他标签、关闭全部,缓存数组同步清理
常见坑
-
❗忘记写
defineOptions({name:'xxx'})→ keep-alive 不生效 -
动态路由路由对象里要把路由的
name带上,mock 路由补充 name 字段{
path: '/system',
component: 'Layout',
name: 'System',
meta: { title: '系统管理', icon: 'Setting' },
children: [
{
path: 'user/list',
component: 'system/user/index',
name: 'UserList',
meta: { title: '用户列表', icon: 'User' }
}
]
} -
如果不需要缓存某个页面:不写组件 name 即可,不会进入 cachedViews