vue 项目中使用 keepAlive 缓存页面,偶尔失效的问题
vue 项目中使用 keepAlive 缓存页面,偶尔失效的问题
背景
用 vue3 开发一个移动端项目,有个需求是,有一个 tab 人员列表页,点击其中的某个人名,可以跳转到该人员的详情页。为了优化体验,我使用了 keepAlive 来缓存人员列表页,这样在跳转详情页后,再返回人员列表页,不需要重新请求接口,直接从缓存中读取数据。
但是,偶尔会出现缓存失效的问题,导致返回人员列表页后,需要重新请求接口。
1、首先从 keepAlive 的缓存配置入手
<!-- App.vue文件 -->
<template>
<vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()" theme-vars-scope="global">
<routerView v-slot="{ Component }">
<div class="absolute top-0 bottom-0 w-full overflow-hidden">
<transition :name="getTransitionName" mode="out-in" appear>
<!-- include后跟的变量用来存放需要缓存的组件name -->
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<!-- 下边component中,不能设置key,否则会导致缓存失效 -->
<component :is="Component" />
</keep-alive>
</transition>
</div>
</routerView>
</vanConfigProvider>
</template>
2、其次,路由中也要进行相应配置
// router/index.ts文件
const routeModuleList: Array<RouteRecordRaw> = [
{
path: '/roster',
name: 'Roster',
redirect: '/roster/index',
component: Layout,
meta: {
title: '人员花名册',
icon: 'friends',
},
children: [
{
path: 'index',
name: 'RosterPage', //注意这个name,需要与组件中的name一致
meta: {
keepAlive: true, 、、//开启缓存
hiddenHeader: true,
},
component: () => import('@/views/roster/index.vue'),
},
],
},
{
path: '/rosterDetail',
name: 'rosterDetail',
meta: {
title: '人员详情',
},
component: () => import('@/views/roster/detail.vue'),
},
]
<!-- /views/roster/index.vue -->
<script setup lang="ts" name="RosterPage"></script>
上边的配置,在部分场景下可以缓存,证明 keepAlive 的功能基本能实现。但是在部分场景下,比如从详情页返回人员列表页,keepAlive 缓存失效,导致需要重新请求接口。折腾半天,才发现忽视了一个细节,详情页的路由层级,跟人员列表页的路由层级不一样。
【注意,上边的路由文件中,人员列表路由的层级是 /roster/index,而详情页的路由层级是 /rosterDetail。那么,为什么层级不一样会导致缓存失效呢?】
原因
路由层级不同,本质上是组件对应的 router-view 层级不同,而 keepAlive 只能缓存它所包裹的 router-view 内渲染的组件。当路由层级变化时,组件挂载的 router-view 可能被卸载,导致缓存的组件实例被销毁,最终表现为 keepAlive 失效。
1、router-view 与路由层级的对应关系
Vue 的路由通过 router-view 渲染匹配到的组件,且 router-view 可以嵌套(对应嵌套路由)。例如:
- 一级路由(如//roster)对应根节点的 router-view(根级 router-view);
- 二级路由(如/roster/index)对应嵌套在一级路由组件内的 router-view(二级 router-view)。
- 不同层级的路由,会被不同层级的 router-view 渲染。
2、路由层级不同导致 router-view 卸载
当从 “列表页”(如二级路由/roster/index)跳转到 “详情页”(如一级路由/rosterDetail)时:
- 列表页对应二级 router-view(嵌套在一级组件内),详情页对应根级 router-view;
- 导航到详情页时,一级组件(包含二级 router-view)会被卸载(因为路由已匹配到根级),导致二级 router-view 及其中的列表组件实例被销毁;
- 此时 keepAlive 缓存的列表组件实例已随 router-view 的卸载而丢失,后退时需要重新创建列表组件,表现为 “强制刷新”。
_ 简单说:不同层级的路由对应不同的 router-view,当路由层级切换时,原层级的 router-view 可能被卸载,导致其中的组件实例脱离 keepAlive 的缓存范围,最终缓存失效。 _
还有一种情况
除了跨路由层级之外,还有个导致 keepAlive “失效”的常见情况——同路由不同参数:例如从/detail?id=1 跳转到/detail?id=2,若路由配置相同(path: '/detail'),Vue Router 会复用同一组件实例(而非创建新实例)。
keepAlive 缓存的是组件实例,所以复用同一组实例, created、mounted 等生命周期不会重新执行。如果你的数据初始化逻辑写在这些钩子中,跳转时参数变化但数据没更新,会让人误以为 “缓存没生效”(实际是缓存生效了,但数据没重新加载)。
解决的办法也很简单,放弃在 created/mounted 中初始化数据,改用 watch 监听 $route.params 或使用 beforeRouteUpdate 导航守卫,在参数变化时重新加载数据。