Vue3 中的 <keep-alive> 详解

<keep-alive> 是 Vue3 内置的抽象组件 (自身不会渲染为真实 DOM 元素),核心作用是缓存包裹在其中的组件实例,保留组件的状态和 DOM 结构,避免组件反复创建和销毁带来的性能损耗,常用于需要保留状态的场景(如标签页切换、列表页返回详情页等)。

一、核心特性与作用

1. 核心功能

  • 缓存组件状态 :被 <keep-alive> 包裹的组件,在切换隐藏时不会触发 unmounted(销毁),而是被缓存起来;再次显示时不会触发 mounted(重新创建),而是恢复之前的状态。
  • 优化性能:避免组件反复创建 / 销毁、数据重新请求、DOM 重新渲染,减少资源消耗。
  • 保留组件上下文:比如表单输入内容、滚动条位置、组件内部的状态数据等,切换后仍能保持原有状态。

2. 关键特点

  • 是抽象组件,不生成 DOM 节点,也不会出现在组件的父组件链中;
  • 仅对动态组件<component :is="componentName">)或路由组件生效;
  • 可通过属性配置缓存规则(指定缓存 / 排除缓存的组件)。

二、基本使用方式

1. 基础用法:包裹动态组件

用于切换多个组件时,缓存不活跃的组件状态:

js 复制代码
<template>
  <div>
    <!-- 切换按钮 -->
    <button @click="currentComponent = 'ComponentA'">组件A</button>
    <button @click="currentComponent = 'ComponentB'">组件B</button>

    <!-- keep-alive 包裹动态组件,缓存组件实例 -->
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

// 控制当前显示的组件
const currentComponent = ref('ComponentA');
</script>

此时切换组件 A/B,组件不会被销毁,再次切换回来时会保留之前的状态(如 ComponentA 中的输入框内容)。

2. 常用场景:包裹路由组件

在路由切换时缓存页面状态(如列表页滚动位置、筛选条件),是项目中最常用的场景:

js 复制代码
<!-- App.vue 或路由出口组件 -->
<template>
  <router-view v-slot="{ Component }">
    <!-- 缓存路由组件 -->
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </router-view>
</template>

三、核心属性:配置缓存规则

<keep-alive> 提供 3 个核心属性,用于灵活控制缓存的组件范围:

1. include:指定需要缓存的组件

  • 类型:String | RegExp | Array

  • 作用:只有名称匹配的组件才会被缓存(组件名称通过 name 选项定义,Vue3 单文件组件中 <script> 内的 name<script setup> 配合 defineOptions({ name: 'xxx' }) 定义)。

  • 示例:

    js 复制代码
    <!-- 字符串(逗号分隔多个组件名) -->
    <keep-alive include="ComponentA,ComponentB">
      <component :is="currentComponent"></component>
    </keep-alive>
    
    <!-- 正则表达式(需用 v-bind 绑定) -->
    <keep-alive :include="/^Component/">
      <component :is="currentComponent"></component>
    </keep-alive>
    
    <!-- 数组(需用 v-bind 绑定) -->
    <keep-alive :include="['ComponentA', 'ComponentB']">
      <component :is="currentComponent"></component>
    </keep-alive>

2. exclude:指定不需要缓存的组件

  • 类型:String | RegExp | Array

  • 作用:名称匹配的组件不会被缓存,优先级高于 include

  • 示例:

    js 复制代码
    <keep-alive exclude="ComponentC">
      <component :is="currentComponent"></component>
    </keep-alive>

3. max:设置缓存组件的最大数量

  • 类型:Number

  • 作用:限制缓存的组件实例数量,当缓存实例超过 max 时,会按照「LRU(最近最少使用)」策略,销毁最久未使用的组件缓存。

  • 示例:

    js 复制代码
    <!-- 最多缓存 3 个组件实例 -->
    <keep-alive :max="3">
      <component :is="currentComponent"></component>
    </keep-alive>

四、缓存组件的生命周期钩子

<keep-alive> 缓存的组件,不会触发 mounted/unmounted,而是触发专属的生命周期钩子:

1. onActivated:组件被激活时触发

  • 时机:缓存的组件从隐藏状态切换为显示状态时(第一次渲染时,会在 mounted 之后触发;后续激活时,仅触发 onActivated)。
  • 用途:恢复组件激活后的状态(如重新监听事件、刷新数据等)。

2. onDeactivated:组件被失活时触发

  • 时机:缓存的组件从显示状态切换为隐藏状态时(不会触发 unmounted)。
  • 用途:清理组件失活后的资源(如取消事件监听、清除定时器等)。

示例:组件内使用钩子

js 复制代码
<!-- ComponentA.vue -->
<template>
  <div>组件A:<input type="text" v-model="inputValue"></div>
</template>

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

const inputValue = ref('');

// 第一次渲染时触发(后续激活不触发)
onMounted(() => {
  console.log('组件A 首次挂载');
});

// 组件被激活时触发(切换显示时)
onActivated(() => {
  console.log('组件A 被激活');
  // 可在此恢复滚动条位置、重新请求最新数据等
});

// 组件被失活时触发(切换隐藏时)
onDeactivated(() => {
  console.log('组件A 被失活');
  // 可在此取消定时器、取消事件监听等
});
</script>

五、高级用法:结合路由配置缓存

在实际项目中,常需要针对特定路由进行缓存,可通过「路由元信息(meta)」配合 <keep-alive> 实现精准缓存:

1. 配置路由元信息

router/index.js 中,给需要缓存的路由添加 meta.keepAlive: true

js 复制代码
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import ListPage from '../views/ListPage.vue';
import DetailPage from '../views/DetailPage.vue';
import HomePage from '../views/HomePage.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: HomePage,
    meta: { keepAlive: false } // 不缓存
  },
  {
    path: '/list',
    name: 'List',
    component: ListPage,
    meta: { keepAlive: true } // 需要缓存
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    component: DetailPage,
    meta: { keepAlive: false } // 不缓存
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

2. 根据路由元信息缓存

在路由出口处,通过 v-if 判断路由的 meta.keepAlive 属性,决定是否缓存:

js 复制代码
<!-- App.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <!-- 缓存需要保留状态的路由组件 -->
    <keep-alive>
      <component
        :is="Component"
        v-if="route.meta.keepAlive"
      />
    </keep-alive>
    <!-- 不缓存的组件直接渲染 -->
    <component
      :is="Component"
      v-if="!route.meta.keepAlive"
    />
  </router-view>
</template>

六、注意事项与常见问题

1. 注意事项

  • <keep-alive> 仅对动态组件或路由组件生效,对普通组件(直接渲染的组件)无效;
  • 组件名称必须正确定义:<script setup> 中需通过 defineOptions({ name: 'XXX' }) 定义组件名,否则 include/exclude 无法匹配;
  • 缓存的组件会占用内存,若缓存过多组件,可能导致内存泄漏,建议通过 max 属性限制缓存数量;
  • 对于需要实时刷新数据的组件,避免使用 <keep-alive>,或在 onActivated 钩子中手动刷新数据。

2. 常见问题

  • 问题 1 :缓存后组件数据不更新?解决方案:在 onActivated 钩子中重新请求数据或更新组件状态,确保激活时获取最新数据。

  • 问题 2include/exclude 配置不生效?解决方案:检查组件名称是否正确定义,正则 / 数组形式是否通过 v-bind 绑定,避免直接写字面量。

  • 问题 3 :路由切换后滚动条位置未保留?解决方案:在 onDeactivated 中记录滚动条位置,在 onActivated 中恢复滚动条位置:

    js 复制代码
    // ListPage.vue
    import { ref, onActivated, onDeactivated } from 'vue';
    
    // 记录滚动条位置
    const scrollTop = ref(0);
    
    onDeactivated(() => {
      // 失活时记录滚动位置
      scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop;
    });
    
    onActivated(() => {
      // 激活时恢复滚动位置
      document.documentElement.scrollTop = scrollTop.value;
      document.body.scrollTop = scrollTop.value;
    });

总结

  1. <keep-alive> 是 Vue3 内置抽象组件,核心作用是缓存组件实例、保留组件状态、优化性能;
  2. 基础用法:包裹动态组件或路由组件,通过 include/exclude/max 配置缓存规则;
  3. 生命周期:缓存组件触发 onActivated(激活)和 onDeactivated(失活),替代 mounted/unmounted
  4. 高级用法:结合路由元信息 meta.keepAlive,实现特定路由的精准缓存;
  5. 注意:合理控制缓存数量,避免内存泄漏,需要实时刷新数据的场景在 onActivated 中手动更新。
相关推荐
其尔Leo2 小时前
Vue3可动态添加行el-table组件
前端
紫小米2 小时前
webpack详解和实操
前端·webpack·node.js
不想秃头的程序员2 小时前
JavaScript 中的深拷贝与浅拷贝详解
前端·面试
风止何安啊2 小时前
用 10 行代码就能当 “服务器老板”+“网络小偷”+“文件管家”?Node.js:别不信!
前端·javascript·node.js
昨晚我输给了一辆AE862 小时前
react-hook-form 初始化值为异步获取的数据的最佳实践
前端·react.js·强化学习
用户841794814562 小时前
vue 表格 vxe-table 实现前端分页、服务端分页的用法
vue.js
PieroPC2 小时前
NiceGUI 内置Material Design图标库
前端
Cache技术分享2 小时前
276. Java Stream API - 使用 flatMap 和 mapMulti 清理数据并转换类型
前端·后端
inferno2 小时前
CSS 基础(第一部分)
前端·css