vue3页面缓存-keepAlive

vue3页面缓存-keepAlive

很多人第一次用 Vue3 做页面缓存,通常都是在外层包一层 <KeepAlive>,结果很快就会遇到几个问题:

  • 不是所有页面都该缓存
  • 返回列表页时,数据保住了,但滚动位置丢了
  • 某些页面离开后其实应该清缓存,否则会看到旧状态

这次在项目里正好落了一版页面缓存,场景是 myOrder 列表页:从列表进入详情再返回时,列表状态和滚动位置都保留;但如果从 myOrder 返回首页,则主动清掉缓存,下次重新进入就是全新页面。

这篇文章就按这次实战改动,拆解一套更可控的 Vue3 KeepAlive 方案。

一、先说结论:不要只会"包一层 KeepAlive"

如果只是这样写:

vue 复制代码
<router-view v-slot="{ Component }">
  <KeepAlive>
    <component :is="Component" />
  </KeepAlive>
</router-view>

它只能解决"组件实例不销毁"这个问题,但还不够。

真正可用的页面缓存,至少要补上 3 个能力:

  1. 只缓存指定页面,而不是全站缓存
  2. 能在特定路由跳转时清除缓存
  3. 能恢复列表滚动位置,而不只是保住响应式数据

这次的实现就是围绕这 3 点展开的。

二、实现目标

先明确这次 myOrder 页面缓存的行为:

  • myOrder 进入其他业务页,再返回 myOrder,列表数据保留
  • 搜索条件、分页状态、接口结果都保留
  • 列表滚动位置保留
  • 但如果从 myOrder 回到 home,则移除缓存,下次重新进入重新加载

这比"永远缓存"更符合真实业务。

三、第一步:在 App.vue 接入 KeepAlive

先在应用入口把 router-view 改成插槽模式,然后接上 <KeepAlive>

vue 复制代码
<router-view v-slot="{ Component }">
  <KeepAlive
    :max="3"
    :include="keepAliveViewNames"
  >
    <component :is="Component" />
  </KeepAlive>
</router-view>

这里有两个重点。

1. include 不写死,改成动态名单

如果直接写成:

vue 复制代码
<KeepAlive include="myOrder" />

后面页面一多会很难维护,所以这里把缓存名单做成响应式数组 keepAliveViewNames,由路由统一控制。

2. max 一定要限制

KeepAlive 本质上是在缓存组件实例,不做上限控制,页面多了之后容易带来内存占用问题。这里我先给了 3,实际项目可以按业务量调整。

四、第二步:单独维护缓存页面名单

我新增了一个文件:src/router/keepAlive.ts

ts 复制代码
import { ref } from 'vue'

const keepAliveInclude = ref<string[]>([])

export const keepAliveViewNames = keepAliveInclude

export const addKeepAliveView = (name: string) => {
  if (!keepAliveInclude.value.includes(name)) {
    keepAliveInclude.value.push(name)
  }
}

export const removeKeepAliveView = (name: string) => {
  keepAliveInclude.value = keepAliveInclude.value.filter(item => item !== name)
}

这层的作用很简单:把"哪些页面该缓存"的控制权,从组件里抽出来,统一交给路由。

这样做有两个好处:

  • 页面组件本身不用关心自己何时加入缓存名单
  • 后面要做"离开某页面时清缓存"也更方便

五、第三步:在路由守卫里动态加入 / 移除缓存

接下来是这次方案的核心。

src/router/index.ts 里加了两段辅助方法:

ts 复制代码
const getKeepAliveName = (route: any) => {
  return route.meta?.keepAliveName as string | undefined
}

const shouldDropKeepAlive = (from: any, to: any) => {
  const routeNames = from.meta?.dropKeepAliveWhenLeaveTo as string[] | undefined

  if (!routeNames?.length) {
    return false
  }

  return routeNames.includes(String(to.name))
}

然后在 beforeEach 里处理:

ts 复制代码
router.beforeEach((to: any, from: any, next: any) => {
  const toKeepAliveName = getKeepAliveName(to)
  const fromKeepAliveName = getKeepAliveName(from)

  if (toKeepAliveName) {
    addKeepAliveView(toKeepAliveName)
  }

  if (fromKeepAliveName && shouldDropKeepAlive(from, to)) {
    removeKeepAliveView(fromKeepAliveName)
  }

  next()
})

这段逻辑的意思是:

  • 进入某个声明了 keepAliveName 的页面时,把它加入缓存名单
  • 离开当前页时,如果目标路由命中了 dropKeepAliveWhenLeaveTo,就把当前页从缓存名单里移除

这样,缓存就不是"永久保留",而是"按跳转关系精确控制"。

六、第四步:把缓存策略写到路由 meta

页面是否缓存,不要在页面里写死,最好挂到路由配置上。

src/router/myOrder.ts 里这样配:

ts 复制代码
export const myOrder = [
  {
    path: '/myOrder',
    name: 'myOrder',
    component: () => import('@/views/myOrder/index.vue'),
    meta: {
      title: 'My Order',
      customBackTo: '/home',
      leftButtonText: '',
      hasRightButton: false,
      keepAliveName: 'myOrder',//新增
      dropKeepAliveWhenLeaveTo: ['home'],//新增
    },
  },
]

这里两个字段分别代表:

  • keepAliveName:进入这个路由时,把哪个组件名加入 include
  • dropKeepAliveWhenLeaveTo:当前页跳到哪些路由时,要清掉当前缓存

这个写法很适合中后台或 H5 业务页,因为很多时候"是否缓存"本来就是路由层面的策略,不是纯组件逻辑。

七、第五步:组件名必须和 include 对上

这是 KeepAlive 最容易踩的坑。

include 匹配的不是路由名,而是组件的 name。所以页面组件里必须显式声明:

vue 复制代码
<script setup lang="ts">
defineOptions({
  name: 'myOrder',
})
</script>

然后这个 name 要和路由 meta 里的 keepAliveName 一致:

ts 复制代码
keepAliveName: 'myOrder'

如果这两个名字对不上,KeepAlive 看起来包上了,实际上不会生效。

八、第六步:缓存组件实例,不等于自动恢复滚动位置

这也是实战里最常见的误区。

很多人以为页面被 KeepAlive 缓存后,返回列表页滚动位置就一定还在。实际上不一定,尤其是滚动容器不是 window、而是页面内部某个容器时,通常还得自己存。

这次在 src/views/myOrder/index.vue 里补了这一段:

ts 复制代码
const contentScrollTop = ref(0)

const getScrollContainer = () => {
  return document.querySelector('.content') as HTMLElement | null
}

onActivated(() => {
  nextTick(() => {
    const container = getScrollContainer()

    if (!container) {
      return
    }

    container.scrollTop = contentScrollTop.value
  })
})

onBeforeRouteLeave(() => {
  const container = getScrollContainer()

  if (!container) {
    return
  }

  contentScrollTop.value = container.scrollTop
})

这里的思路是:

  • 离开页面前,记录当前滚动容器的 scrollTop
  • 页面再次被激活时,用 onActivated 把滚动位置恢复回去

为什么不用 onMounted

因为被 KeepAlive 缓存的组件再次显示时,不会重新走 onMounted,而是走 onActivated。这点一定要分清。

九、这套方案为什么比"全局缓存"更稳

我更推荐这套方案,原因有 4 个。

1. 缓存范围可控

不是所有页面都缓存,只缓存真正需要"返回保留状态"的页面。

2. 支持定向失效

比如这次 myOrder -> home 时主动失效,就能避免用户从首页重新进入后看到旧列表状态。

3. 路由策略集中

缓存规则都挂在 route.meta 和路由守卫里,不会散落在各个页面组件中。

4. 更接近真实业务

很多业务不是"要不要缓存"这么简单,而是:

  • 从 A 到 B 要缓存
  • 从 A 到首页不要缓存
  • 从筛选页返回要保留条件
  • 从主入口重新进入要重新加载

这套写法正好能覆盖这类需求。

十、几个容易踩坑的点

1. include 匹配的是组件名,不是路由名

这一点最关键。你路由叫 myOrder 不代表就能缓存,组件必须有:

ts 复制代码
defineOptions({ name: 'myOrder' })

2. 只缓存组件,不会自动处理你的副作用

比如:

  • 定时器
  • WebSocket
  • 页面轮询
  • 滚动恢复

这些都要自己在 onActivated / onDeactivated 或路由生命周期里处理。

3. 不要滥用缓存

以下页面通常不建议缓存:

  • 数据强依赖实时性的页面
  • 很重的大表格 / 地图 / 富媒体页面
  • 离开后必须清空敏感数据的页面

4. max 不要省

缓存越多,实例越多。移动端 H5 更要注意上限。

十一、可以直接复用的落地步骤

如果你也要在项目里加页面缓存,可以按这个顺序来:

  1. App.vuerouter-view 外层加 <KeepAlive>
  2. 新建一个 keepAlive.ts,统一维护 include 名单
  3. 在路由 beforeEach 里处理缓存加入和移除
  4. 在目标路由 meta 上声明 keepAliveName
  5. 在页面组件里显式声明 defineOptions({ name })
  6. 如果是列表页,再补 onBeforeRouteLeave + onActivated 做滚动恢复

这套流程改动不大,但已经能覆盖大多数"列表页返回保留现场"的场景。

十二、最后总结

Vue3 的页面缓存,核心不是"会不会用 <KeepAlive>",而是你有没有把这几个问题想清楚:

  • 哪些页面该缓存
  • 什么时候该失效
  • 页面返回时要恢复什么

我这次的落地方案,本质上就是:

  • KeepAlive 保住组件实例
  • 用路由 meta + beforeEach 控制缓存名单
  • onActivated + onBeforeRouteLeave 恢复滚动现场

如果你的项目里也有"列表进详情再返回"的需求,这套方案基本可以直接复用。

如果你后面还想继续扩展,也可以在这套基础上再加:

  • 按 query / params 维度决定是否复用缓存
  • 进入页面时按条件强制刷新数据
  • 封装成通用的 keepAlive 路由管理方案
相关推荐
一个有理想的摸鱼选手2 小时前
在三维地球上以另一种视角感受巴威台风
前端·gis·ai编程
程序员黑豆3 小时前
鸿蒙应用开发:Grid组件实现九宫格布局教程
前端·华为·harmonyos
浮江雾3 小时前
Flutter第十七节-----路由管理(3)
android·开发语言·前端·javascript·flutter·入门
阿懂在掘金3 小时前
企业级命令式弹窗方案:三行代码适配已有 Dialog
前端·vue.js·前端框架
Revolution614 小时前
测试接口为什么进了生产包:前端环境变量到底在什么时候生效
前端·前端工程化
午安~婉4 小时前
Git中SSH连接
前端·git·gitee
盏灯4 小时前
mac 外接磁盘,热更新失效
前端·后端
那些年丶ny4 小时前
颜色扩展库
前端·javascript·数据可视化
ihuyigui4 小时前
海外签收通知短信接口
android·java·开发语言·前端·数据库·后端