技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue 中 KeepAlive 的深入理解和使用

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/38-Vue中keep-alive的深入理解和使用
本文目录9 个章节
  1. 一、什么是 KeepAlive?
  2. 二、基本用法
  3. 三、activated 和 deactivated
  4. 四、include、exclude 和 max
  5. 五、配合 Vue Router 使用
  6. 六、常见误区和防坑指南
  7. 七、总结
  8. 官方参考
  9. 原文出处

Vue 中 KeepAlive 的深入理解和使用

原文主要介绍 Vue 2 的 <keep-alive>。本文保留原文关于组件缓存、includeexcludemax、路由和 activated/deactivated 的内容,并补充 Vue 3.5 与 Vue Router 4 的写法。

一、什么是 KeepAlive

KeepAlive 是 Vue 内置组件,用于缓存动态组件的组件实例。当组件从当前视图切换出去时,Vue 可以把它停用而不是立即卸载;下次切换回来时复用原来的实例和状态。

<KeepAlive>
  <component :is="currentComponent" />
</KeepAlive>

缓存的是组件实例及其子树的运行状态,不是把所有 DOM 永久固定在页面上。被缓存的组件在非活动期间通常会从 DOM 树中移出,但不会执行完整的卸载流程。

因此要区分:

  • 第一次显示:仍然会创建组件并执行 mounted
  • 切换离开:执行 deactivated,组件实例保留在缓存中;
  • 再次显示:执行 activated,不会重新执行一次 mounted
  • 父级卸载、缓存被淘汰或不再满足缓存条件:组件才会真正卸载,执行 unmounted(Vue 3)或 destroyed(Vue 2)。

二、基本用法

Vue 3 动态组件

<script setup>
import { ref } from 'vue'
import HomePanel from './HomePanel.vue'
import ProfilePanel from './ProfilePanel.vue'

const currentComponent = ref(HomePanel)
</script>

<template>
  <button @click="currentComponent = HomePanel">首页</button>
  <button @click="currentComponent = ProfilePanel">个人信息</button>

  <KeepAlive>
    <component :is="currentComponent" />
  </KeepAlive>
</template>

如果没有 KeepAlive,切换动态组件通常会卸载旧组件并创建新组件;加入 KeepAlive 后,组件内部输入框、滚动位置和局部状态可以被保留。

Vue 2 的写法

<keep-alive>
  <component :is="currentTabComponent" />
</keep-alive>

Vue 2 中组件名一般来自组件的 name 选项或注册名称;Vue 3 中 include/exclude 也主要依据组件的 name 匹配。Vue 3.2+ 使用 <script setup> 时,组件名通常可以根据文件名推断;需要精确控制时可显式声明:

<script setup>
defineOptions({
  name: 'ProfilePanel',
})
</script>

defineOptions 是 Vue 3.3+ 的编译器宏;Vue 2 应继续使用:

export default {
  name: 'ProfilePanel',
}

三、activateddeactivated

KeepAlive 包裹的有状态组件可以使用两组生命周期:

Options API

export default {
  activated() {
    // 再次进入缓存组件时执行,也可在第一次激活时执行
    this.refreshIfNeeded()
  },
  deactivated() {
    // 暂时离开页面但仍留在缓存中
    this.pausePolling()
  },
}

Composition API

<script setup>
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  console.log('组件被激活')
})

onDeactivated(() => {
  console.log('组件被停用,但可能仍在缓存中')
})
</script>

deactivated 适合暂停轮询、停止动画、移除临时监听;activated 适合重新检查数据或恢复轮询。全局事件监听和定时器仍然要在 onUnmounted 中做最终清理,因为组件可能长期留在缓存中,甚至直到父组件销毁才卸载。

<script setup>
import {
  onActivated,
  onDeactivated,
  onUnmounted,
} from 'vue'

let timer

function startPolling() {
  if (timer == null) {
    timer = window.setInterval(() => {
      console.log('poll')
    }, 5000)
  }
}

function stopPolling() {
  if (timer != null) {
    window.clearInterval(timer)
    timer = undefined
  }
}

onActivated(startPolling)
onDeactivated(stopPolling)
onUnmounted(stopPolling)
</script>

四、includeexcludemax

KeepAlive 常用的三个 props:

  • include:只有名称匹配的组件才缓存;
  • exclude:名称匹配的组件不缓存;
  • max:最多缓存的组件实例数量,超出时淘汰最久未使用的实例。

它们支持字符串、正则表达式或数组。绑定数组和正则表达式时必须使用 v-bind

<KeepAlive
  :include="['HomePanel', 'ProfilePanel']"
  exclude="ExpensivePreview"
  :max="5"
>
  <component :is="currentComponent" />
</KeepAlive>

字符串也可以使用逗号分隔的组件名:

<KeepAlive include="HomePanel,ProfilePanel">
  <component :is="currentComponent" />
</KeepAlive>

当前实现可以概括为:组件名称需要满足 include(如果存在),同时不能命中 exclude:

matches(include, name) && !matches(exclude, name)

所以当同一个组件同时匹配 includeexclude 时,不会被缓存,exclude 的排除效果优先。

<KeepAlive include="HomePanel,ProfilePanel" exclude="ProfilePanel">
  <component :is="currentComponent" />
</KeepAlive>

max 使用近似 LRU(最近最少使用)策略管理缓存。达到上限时,最久没有被访问的缓存实例会被移除;被移除的实例会进入真正的卸载流程,而不是只触发一次 deactivated

五、配合 Vue Router 使用

Vue Router 3 的历史写法

原文中的直接包裹方式在 Vue 2/Vue Router 3 项目中仍可能看到:

<keep-alive>
  <router-view />
</keep-alive>

它会尝试缓存所有匹配到的路由组件。若只缓存部分组件,可以通过 include/exclude 或路由组件名称控制。

Vue Router 4 的推荐写法

Vue Router 4 推荐使用 router-view 的插槽拿到实际的路由组件,再决定是否包裹:

<template>
  <RouterView v-slot="{ Component }">
    <KeepAlive :include="['HomePage', 'ProfilePage']">
      <component :is="Component" />
    </KeepAlive>
  </RouterView>
</template>

KeepAlive 匹配的是实际路由组件的 name,不是路由的 name 字段。路由配置中的 name 和组件的 name 是两个不同概念。

如果使用路由 meta 控制是否缓存,可以写成:

<template>
  <RouterView v-slot="{ Component, route }">
    <KeepAlive v-if="route.meta.keepAlive">
      <component :is="Component" />
    </KeepAlive>
    <component v-else :is="Component" />
  </RouterView>
</template>

路由配置:

import type { RouteRecordRaw } from 'vue-router'

export const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('./views/HomePage.vue'),
    meta: { keepAlive: true },
  },
  {
    path: '/profile',
    name: 'profile',
    component: () => import('./views/ProfilePage.vue'),
    meta: { keepAlive: false },
  },
]

如果同一个组件需要根据不同参数保留不同实例,可以给动态组件设置稳定且有区分度的 key:

<RouterView v-slot="{ Component, route }">
  <KeepAlive>
    <component :is="Component" :key="route.fullPath" />
  </KeepAlive>
</RouterView>

是否需要 key 取决于业务:不设置 key 可能复用同一组件类型的实例;设置 fullPath 会把不同参数/查询条件视为不同缓存项,也会增加缓存数量。

六、常见误区和防坑指南

1. KeepAlive 不会让组件永远 mounted

“被缓存的组件不会 mounted”是错误的。第一次创建并挂载时仍然会执行 mounted;之后从缓存中恢复主要执行 activated

2. deactivated 不等于销毁

组件离开当前视图时,可能只是被停用。不要在 deactivated 中假设组件已经销毁,也不要只在这里做一次性永久资源清理;最终资源释放应放在 onUnmounted/unmounted

3. 组件必须有可匹配的 name

include/exclude 依赖组件 name。Vue 2 可以使用 name 选项或注册名;Vue 3 <script setup> 可以使用文件名推断,也可以使用 defineOptions({ name }) 显式设置。

4. includeexclude 使用正则、数组时要绑定

<KeepAlive :include="/^(Home|Profile)/" />
<KeepAlive :include="['HomePage', 'ProfilePage']" />

不绑定时,数组或正则表达式可能被当成普通字符串处理。

5. 函数组件通常没有需要缓存的组件实例状态

KeepAlive 的价值在于缓存有状态组件实例。无状态函数式组件没有同样的实例生命周期,不应把它当作主要缓存对象。

6. 缓存不是数据同步方案

被缓存组件中的数据不会自动因为路由进入而重新请求。需要刷新时可以在 onActivated 中调用请求,也可以由 Pinia、query cache 或父组件统一管理数据。

7. 不要用 $destroy 清理 Vue 3 缓存

Vue 2 项目可能使用 $destroy 或修改内部缓存;Vue 3 已移除 $destroy,也没有推荐的公开 API 让业务直接操作 KeepAlive 内部 cache。应通过改变 include、切换 key 或调整组件结构实现明确的缓存策略。

8. TeleportKeepAlive 不是一回事

KeepAlive 缓存组件实例,Teleport 改变 DOM 插入位置。弹窗既可以被 KeepAlive 缓存,也可以使用 Teleport 放到 body,但两者解决的是不同问题。

七、总结

  • KeepAlive 缓存动态组件实例,使组件离开视图时停用而不是立即卸载。
  • 第一次显示仍会执行 mounted;重新进入执行 activated;真正淘汰或父级卸载时才执行 unmounted/destroyed。
  • includeexclude 按组件 name 匹配,exclude 对同时命中的组件具有排除效果。
  • max 会限制缓存数量并淘汰最久未使用的实例。
  • Vue Router 4 推荐通过 RouterView 插槽包裹实际的 Component
  • 需要暂停/恢复任务时使用 onActivated/onDeactivated,最终清理仍放在 onUnmounted

官方参考

原文出处

作者:Junyang

原文链接:https://juejin.cn/post/6844903918313406472

来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS