使用KeepAlive动态缓存标签页
很多管理系统都有已打开的标签功能,类似于现在浏览器的顶部标签栏,打开过的页面一般会缓存一段时间,在这段时间内打开,页面不会重新加载.基于vue 2.x版本的keep-alive
标签可以实现缓存页面的需求,但是如何去除缓存呢?
假设这样一个场景,用户打开了标签页-A和标签页-B,停留在标签-A,用户希望重新加载下标签页-B,这个时候keep-alive已经缓存了两个页面,如何单独去掉B页的缓存,使得B页面可以重新进入?
keep-alive的基本功能
可以传入的三个props:
- include 字符串或正则表达式。只有名称匹配的组件会被缓存
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
<keep-alive>
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition>
相似,<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
当组件在 <keep-alive>
内被切换,它的 activated
和 deactivated
这两个生命周期钩子函数将会被对应执行。
在 Vue 2.2.0 及其更高版本中,activated 和 deactivated 将会在 ``` 树内的所有嵌套组件中触发。
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
存储需要缓存的页面
首先想到的是在路由守卫中把需要的RouteInfo
存到Store或者其他什么地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| router.beforeEach(async (to, from, next)=> { if ( [ "Login", "Dashboard", "NotFound", "InternalError", "Unauthorized", ].includes(to.name) ) { return next(); } else { if (!getToken()) { return next({ name: "Login"}); } else if (anyMenuMatch(store.state.menuList, to.path)) { store.commit("ADD_PAGE_TAG", to); return next(); } else { return next({ name: "Unauthorized"}); } } })
|
在Vuex中存储已经缓存的页面路由信息,主要使用RouteInfo.path
字段来区分页面,使用RouteInfo.meta.title
来确定标签页名称,使用RouteInfo.name
来确定缓存的组件名称,所有所有的RouteInfo的name字段需要和它import()
的组件的name
属性一致,如果是使用@vue/composition-api
的setup script的组件,需要额外写个普通script
,导出一个含name
的object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export default new Vuex.Store({ state: { keepAlivePages: [], }, mutations:{ ADD_PAGE_TAG(state, page) { const oldIndex = state.keepAlivePages.findIndex( (p) => page.path === p.path ); if (oldIndex < 0) { state.keepAlivePages.push(page); } } } })
|
在router中指定path
,meta
,name
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const constantRoutes = { { path: "/xxx", name: "AComponent", meta: { title: "仓储", }, component: () => import("/path/to/component/AComponent"), } }
const router = new Router({ routes: [...constantRoutes, ...functionalRoutes], });
export default router;
|
在Component中指定name
:
1 2 3 4 5 6
| <script> export default { name: "AComponent", }; </script>
|
在合适位置展示已访问的页面
使用合适组件展示下用户已经访问的页面标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div class="flex justify-start px-5 space-x-1" v-if="keepAlivePages && keepAlivePages.length >= 2" > <el-tag v-for="(page, index) of keepAlivePages" :key="page.path" closable class="cursor-pointer rounded-b-none" @click="handleNavigate(page)" > {{ page.meta.title }} </el-tag> </div> <div v-else></div> </template> <script setup> import { useStore, useRoute, useRouter } from "@/composables"; import { ref, computed } from "@vue/composition-api"; const store = useStore(); const route = useRoute(); const router = useRouter(); const keepAlivePages = computed(() => { return store.state.keepAlivePages; });
function handleNavigate(page) { if (route.value.path !== page.path) { router.push(page); } } </script>
|
移除已缓存的页面
<keep-alive>
标签的exclude
优先级比include
高,我们可以把手工移除(点叉叉)的页面加到exclude
,也就是维护一个include列表一个exclude列表,新访问页面的时候吧页面的RouteInfo
push到include列表,并且从exclude列表移除相同path的(如果有的话),叉掉页面时,把关掉的页面RouteInfo
push到exclude列表,并且从include列表移除相同path的(如果有的话),看下在Vuex mutations中的简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| export default new Vuex.Store({ state: { keepAlivePages: [], excludeKeepAlivePages: [], }, mutations:{ ADD_PAGE_TAG(state, page) { const oldIndex = state.keepAlivePages.findIndex( (p) => page.path === p.path ); if (oldIndex < 0) { state.keepAlivePages.push(page); } const deleteIndex = state.excludeKeepAlivePages.findIndex( (p) => page.path === p.path ); if (deleteIndex >= 0) { state.excludeKeepAlivePages.splice(deleteIndex, 1); } }, REMOVE_PAGE_TAG(state, page) { const deleteIndex = state.keepAlivePages.findIndex( (p) => page.path === p.path ); if (deleteIndex >= 0) { state.keepAlivePages.splice(deleteIndex, 1); } const addIndex = state.excludeKeepAlivePages.findIndex( (p) => page.path === p.path ); if (addIndex < 0) { state.excludeKeepAlivePages.push(page); } }, } })
|
然后在标签页组件中处理下关闭标签的事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
const keepAlivePages = computed(() => { return store.state.keepAlivePages; });
function handleCloseSelf(page, index) { const allPages = keepAlivePages.value; if (allPages.length === 1) { return router.push({ name: "Dashboard" }); } if (index >= 1) { return router.push(allPages[index - 1]); } if (index === 0) { return router.push(allPages[index + 1]); } }
function handleClosePageTag(page, index) { if (route.value.path === page.path) { handleCloseSelf(page, index); } store.commit("REMOVE_PAGE_TAG", page); }
|
keep-alive路由部分
在需要缓存页面的router-view
节点上加keep-alive
,这里我还加了开关用于动态启用关闭缓存功能,也是存在vuex
中的一个备份值openPageKeepAlive
.
1 2 3 4 5 6
| <keep-alive :include="openPageKeepAlive ? keepAlivedNames : []" :exclude="openPageKeepAlive ? excludeKeepAlivedNames : []" > <router-view/> </keep-alive>
|