预期效果:(借助
iview-ui
的在线体验页面示意一下)
- 项目中只有一部分页面需要缓存,且存在多级路由的页面。
- 每打开一个菜单,就会新增一个 Tab标签,只要 Tab标签不关闭,对应的页面就会被缓存,Tab标签关闭时,页面才会被销毁。
以下是我自己琢磨出来的方案,可能有所欠缺,多多包涵,可以先翻到最后先大致了解一下相关逻辑。
代码实现
1. router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: () => import('xxx/Login')
},
{
path: '/myProfix', // 自定义的顶层路由前缀
component: () => import('xxx/Main'),
name: 'Main',
redirect: { name: 'Home' },
children: [
{
path: 'home',
name: 'Home',
component: () => import('xxx/Home'),
meta: { keepAlive: true } // 需要缓存的页面路由在 meta中添加 keepAlive属性
},
{
path: 'about',
name: 'About',
component: () => import('xxx/About')
},
// 多级路由
{
path: 'father',
name: 'Father',
component: () => import('xxx/Father'),
redirect: { name: 'Son0' },
children: [
{
path: 'son0',
name: 'Son0',
component: () => import('xxx/Son0'),
meta: { keepAlive: true }
},
{
path: 'son1',
name: 'Son1',
component: () => import('xxx/Son1')
},
]
}
]
}
]
const router = new VueRouter({
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
// 当前路由如支持缓存,将当前路由对应的页面组件名添加到 store中
if (to.meta?.keepAlive) {
// 支持多级路由,多级路由存顶级父级路由的组件名(这个顶级父路由是指顶层路由 /myProfix的下一级,示例中的路由 /myProfix下只有2 层,实际可以有 2层以上)
store.dispatch('updateKeepAliveRouteComponents', { operation: 'add', value: to.matched[1]?.name || to.name })
}
next()
})
export default router
2. store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
keepAliveRouteComponents: [] // 需要缓存的路由对应的组件名称(此处组件名称与路由名称设置成一样,方便关闭 Tab标签时做匹配处理)
},
actions: {
// 更新 keepAliveRouteComponents
updateKeepAliveRouteComponents: ({ commit }, data) => {
commit('UPDATE_KEEP_ALIVE_ROUTE_COMPONENTS', data)
}
},
mutations: {
// 关闭 Tab标签
CLOSE_TAB (state, payload) {
// ...此处省略关闭 Tab标签的逻辑
// 删除 keepAliveRouteComponents中对应的组件名,以清除对应路由组件的缓存
const curRouteName = ... // 根据实际情况获取到关闭 Tab标签对应的页面路由的 name
this.commit('UPDATE_KEEP_ALIVE_ROUTE_COMPONENTS', { operation: 'delete', value: curRouteName })
},
// 更新 keepAliveRouteComponents
UPDATE_KEEP_ALIVE_ROUTE_COMPONENTS (state, data) {
const { operation, value } = data
if (operation === 'add' && !state.keepAliveRouteComponents.includes(value)) {
state.keepAliveRouteComponents.push(value)
}
if (operation === 'delete') {
const valueIndex = state.keepAliveRouteComponents.indexOf(value)
if (valueIndex !== -1) {
state.keepAliveRouteComponents.splice(valueIndex, 1)
}
}
},
// 退出登录
LOGOUT (state) {
// ...
// 清空 keepAliveRouteComponents,清除所有缓存的页面组件——其实也可以不清空,因为退出到 Login页面时, Main组件会被销毁
state.keepAliveRouteComponents = []
},
}
})
3. App.vue
<template>
<div id="app">
<router-view />
</div>
</template>
4. Main.vue
<template>
<div class="main">
<Navigation />
<div class="main-view">
// include属性与 store记录的 keepAliveRouteComponents绑定
<keep-alive :include="keepAliveRouteComponents">
<router-view />
</keep-alive>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Navigation from '@system/components/Navigation'
export default {
components: {
Navigation
},
computed: {
...mapState(['keepAliveRouteComponents']),
}
}
</script>
5. Home.vue
<template>
<div class="home"></div>
</template>
<script>
export default {
name: 'Home' // 组件名称与路由名称设置成一样
}
</script>
6. Father.vue
<template>
<div class="father">
<keep-alive :include="['Son0']">
<router-view />
</keep-alive>
</div>
</template>
<script>
export default {
name: 'Father' // 组件名称与路由名称设置成一样
}
</script>
7. Son0.vue
<template>
<div class="son0"></div>
</template>
<script>
export default {
name: 'Son0' // 组件名称与路由名称设置成一样
}
</script>
逻辑总结
- 需要缓存的页面路由添加
meta
属性{ keepAlive: true }
- 页面路由对应的组件名称与路由名称设置成一样,方便 Tab标签关闭时做匹配(这一步不是必须的,可以换成别的方法,只要 Tab标签关闭时,能有相关数据识别到对应的页面的组件名称即可)
- 在
store
中保存需要缓存的页面组件的名称列表keepAliveRouteComponents
<Main>
组件中 组件的include
属性与keepAliveRouteComponents
绑定- 路由全局前置守卫中判断即将进入的路由
keepAlive
属性为true
时,在keepAliveRouteComponents
列表中加入当前路由的name
,如果是多级路由即加入当前路由的顶级父级路由(/myProfix
的下一级)的name
(——可以统一通过$route.matched[1]
来获取需要的name
,为什么取matched[1]
,因为当前还有一层顶级的路由/myProfix
在matched[0]
)(此处记录路由的name
,是基于将页面路由对应的组件名称与路由名称设置成一样的前提条件下) - Tab标签关闭时,删除
keepAliveRouteComponents
中对应的组件名,以清除对应路由组件的缓存 - 多级路由的情况,内部子路由
<router-view />
包裹<keep-alive>
,并根据具体子路由是否缓存添加include
属性(——如果这个多级路由只有一个子路由,也可以不设置) - 系统登出时,清空
keepAliveRouteComponents
(——其实清不清空无所谓,因为跳转到login
页面时,整个<Main>
组件都会被销毁)