KeepAlive缓存组件

文章目录

  • 前言
  • 一、基本用法
    • [1.1 包裹动态组件](#1.1 包裹动态组件)
    • [1.2 配合 router-view](#1.2 配合 router-view)
  • 二、生命周期变化
    • [2.1 首次进入 vs 再次激活](#2.1 首次进入 vs 再次激活)
    • [2.2 activated / deactivated 示例](#2.2 activated / deactivated 示例)
  • [三、include / exclude / max](#三、include / exclude / max)
    • [3.1 include:白名单缓存](#3.1 include:白名单缓存)
    • [3.2 exclude:黑名单排除](#3.2 exclude:黑名单排除)
    • [3.3 max:限制缓存数量(LRU)](#3.3 max:限制缓存数量(LRU))
    • [3.4 组件 name 要求](#3.4 组件 name 要求)
  • 四、典型场景
    • [4.1 多 Tab 切换保留状态](#4.1 多 Tab 切换保留状态)
    • [4.2 列表页 → 详情页 → 返回](#4.2 列表页 → 详情页 → 返回)
    • [4.3 路由 meta 控制缓存](#4.3 路由 meta 控制缓存)
  • 五、注意事项
    • [5.1 只对直接子组件生效](#5.1 只对直接子组件生效)
    • [5.2 内存占用](#5.2 内存占用)
    • [5.3 activated 与 mounted 的分工](#5.3 activated 与 mounted 的分工)
  • 六、面试聚焦
    • [6.1 列表页 → 详情页 → 返回保持状态](#6.1 列表页 → 详情页 → 返回保持状态)
    • [6.2 KeepAlive 缓存会占用内存吗?](#6.2 KeepAlive 缓存会占用内存吗?)
    • [6.3 script setup 中如何指定组件名称?](#6.3 script setup 中如何指定组件名称?)
  • 七、易混淆点
  • 八、思考与练习
  • 总结

前言

<KeepAlive> 是 Vue 内置组件,用于缓存动态组件实例,避免反复创建和销毁。切换被缓存的组件时,滚动位置、表单输入等状态得以保留。本篇会讲清楚:

  • KeepAlive 的工作原理
  • activated / deactivated 生命周期
  • include / exclude / max 缓存策略
  • 典型应用场景与注意事项

一、基本用法

1.1 包裹动态组件

vue 复制代码
<template>
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

<script setup>
import { ref, shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'

const tabs = { a: TabA, b: TabB }
const currentTab = shallowRef(TabA)
</script>

没有 KeepAlive :切换 Tab 时旧组件 unmounted、新组件 mounted,状态丢失。

有 KeepAlive :旧组件 deactivated(隐藏但不销毁),新组件 activated,切回时状态仍在。

1.2 配合 router-view

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

列表页 → 详情页 → 返回列表页时,列表的滚动位置和已加载数据得以保留。


二、生命周期变化

2.1 首次进入 vs 再次激活

复制代码
首次进入缓存组件:
  setup → onBeforeMount → onMounted → onActivated

离开缓存组件(切换到其他):
  onDeactivated(不会 onUnmounted)

再次回到缓存组件:
  onActivated(不会重新 onMounted)

真正销毁缓存(超出 max 或 include 不匹配):
  onDeactivated → onBeforeUnmount → onUnmounted

2.2 activated / deactivated 示例

vue 复制代码
<script setup>
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue'

onMounted(() => {
  console.log('首次挂载,拉取数据')
  fetchList()
})

onActivated(() => {
  console.log('组件被激活(含首次和再次显示)')
  // 从详情页返回时刷新列表(可选)
  refreshList()
})

onDeactivated(() => {
  console.log('组件被缓存隐藏')
  // 暂停定时器、停止监听等
})

onUnmounted(() => {
  console.log('缓存被淘汰,真正销毁')
})
</script>
钩子 触发时机
onMounted 首次挂载(只一次)
onActivated 每次显示(含首次)
onDeactivated 每次隐藏
onUnmounted 缓存被淘汰时

三、include / exclude / max

3.1 include:白名单缓存

vue 复制代码
<KeepAlive :include="['UserList', 'OrderList']">
  <component :is="currentView" />
</KeepAlive>

只有 nameUserListOrderList 的组件会被缓存,其余正常销毁。

3.2 exclude:黑名单排除

vue 复制代码
<KeepAlive :exclude="['Login', 'Register']">
  <component :is="currentView" />
</KeepAlive>

除 Login、Register 外,其余组件都缓存。

3.3 max:限制缓存数量(LRU)

vue 复制代码
<KeepAlive :max="5">
  <component :is="currentView" />
</KeepAlive>

最多缓存 5 个组件实例,超出时按 LRU(最近最少使用) 策略淘汰最久未访问的实例。

复制代码
访问顺序:A → B → C → D → E → F
缓存满(max=5)后访问 F → 淘汰 A(最久未用)
再访问 A → A 重新挂载,可能淘汰 B

3.4 组件 name 要求

include / exclude 基于组件 name 匹配,不是文件名或路由名。

vue 复制代码
<!-- Options API -->
<script>
export default {
  name: 'UserList'
}
</script>

<!-- script setup:需显式声明 -->
<script setup>
defineOptions({ name: 'UserList' })
</script>

<script setup> 中组件名默认取文件名,若与 include 不匹配需用 defineOptions 显式指定。


四、典型场景

4.1 多 Tab 切换保留状态

vue 复制代码
<template>
  <div class="tabs">
    <button v-for="tab in tabList" :key="tab.name" @click="current = tab.comp">
      {{ tab.label }}
    </button>
  </div>

  <KeepAlive :max="3">
    <component :is="current" />
  </KeepAlive>
</template>

各 Tab 的表单输入、滚动位置在切换后保留。

4.2 列表页 → 详情页 → 返回

vue 复制代码
<!-- App.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <KeepAlive :include="['UserList', 'OrderList']">
      <component :is="Component" :key="route.name" />
    </KeepAlive>
  </router-view>
</template>
vue 复制代码
<!-- UserList.vue -->
<script setup>
defineOptions({ name: 'UserList' })

const list = ref([])
const scrollTop = ref(0)

onMounted(() => fetchList())

onActivated(() => {
  // 从详情返回时可选择刷新或保持
})
</script>

列表滚动位置、筛选条件、已加载数据在返回时保留,无需重新请求。

4.3 路由 meta 控制缓存

javascript 复制代码
const routes = [
  { path: '/user', component: UserList, meta: { keepAlive: true } },
  { path: '/user/:id', component: UserDetail, meta: { keepAlive: false } },
  { path: '/order', component: OrderList, meta: { keepAlive: true } }
]
vue 复制代码
<template>
  <router-view v-slot="{ Component, route }">
    <KeepAlive>
      <component
        :is="Component"
        v-if="route.meta.keepAlive"
        :key="route.name"
      />
    </KeepAlive>
    <component
      :is="Component"
      v-if="!route.meta.keepAlive"
      :key="route.path"
    />
  </router-view>
</template>

按路由 meta 灵活控制哪些页面缓存。


五、注意事项

5.1 只对直接子组件生效

vue 复制代码
<!-- ✅ 正确:KeepAlive 直接包裹动态组件 -->
<KeepAlive>
  <component :is="cur" />
</KeepAlive>

<!-- ❌ 错误:中间有 div 包裹,KeepAlive 无法识别 -->
<KeepAlive>
  <div>
    <component :is="cur" />
  </div>
</KeepAlive>

多层嵌套时,每一层动态组件需各自包裹 KeepAlive。

5.2 内存占用

缓存的组件实例及其 DOM、数据都会保留在内存中。缓存过多会导致内存增长,应:

  • 合理设置 max 限制数量
  • include 只缓存必要页面
  • onDeactivated 中清理定时器、大对象

5.3 activated 与 mounted 的分工

javascript 复制代码
// onMounted:仅首次挂载执行,适合一次性初始化
onMounted(() => initChart())

// onActivated:每次显示都执行,适合返回时刷新
onActivated(() => refreshData())

需要「每次显示都更新」的逻辑放 onActivated;只需执行一次的放 onMounted


六、面试聚焦

6.1 列表页 → 详情页 → 返回保持状态

<KeepAlive> 包裹 <router-view>,列表组件设置 name 并加入 include。离开列表时触发 deactivated 而非 unmounted,返回时 activated 恢复,滚动位置和数据保留。

6.2 KeepAlive 缓存会占用内存吗?

。每个缓存实例包含组件状态、DOM 节点、事件监听等。应通过 max 限制数量,用 include 精确控制,避免无节制缓存。

6.3 script setup 中如何指定组件名称?

javascript 复制代码
defineOptions({ name: 'UserList' })

include / exclude 匹配的是组件 name,不是文件名。


七、易混淆点

  1. KeepAlive 不是不销毁 :超出 max 或不在 include 中的组件仍会 unmounted
  2. include 匹配 name 不是路由名 :需在组件内声明 namedefineOptions
  3. onActivated 含首次 :首次进入也会触发,与 onMounted 可能同时执行。
  4. 只对直接子组件生效:中间不能有多余包裹层。
  5. 缓存 ≠ 数据持久化:刷新页面缓存丢失,需持久化请用 localStorage 或 Pinia 插件。

八、思考与练习

1. KeepAlive 的作用是什么?

解析:缓存动态组件实例,切换时不销毁,保留组件状态(滚动位置、表单输入等)。

2. 被缓存组件切换时触发的生命周期?

解析:离开时 deactivated,再次显示时 activated;不会重复 mounted/unmounted(除非缓存被淘汰)。

3. include 和 exclude 的区别?

解析:include 是白名单,只缓存匹配的;exclude 是黑名单,匹配的不缓存,其余都缓存。

4. max 属性如何工作?

解析:限制最大缓存数,超出时按 LRU 策略淘汰最久未访问的组件实例。

5. 如何让列表页返回时保持滚动位置?

解析:列表组件 defineOptions({ name: 'UserList' }),router-view 外包 KeepAlive 并 include 该 name。


总结

  • KeepAlive:缓存动态组件实例,切换时 deactivated/activated 替代 unmounted/mounted
  • include / exclude:按组件 name 控制缓存白名单/黑名单
  • max + LRU:限制缓存数量,淘汰最久未用的实例
  • 典型场景:Tab 切换、列表→详情→返回、多步骤表单
  • 注意:组件需声明 name、只对直接子组件生效、合理控制内存