VUE中的keep-alive详解和应用详细教程

一、概述

在 Vue 应用中,组件的创建与销毁会带来一定的性能开销,并且在切换视图时,组件的状态(如表单数据、滚动位置等)很容易丢失。Vue 官方提供了 <keep-alive> 组件,用于缓存动态组件的实例,从而避免重复渲染与数据丢失。本文将从原理、常用属性、生命周期钩子、典型应用场景等方面,结合代码示例与图解,帮助你深入理解并灵活应用 keep-alive


二、keep-alive 原理

<keep-alive> 是 Vue 内置的抽象组件(abstract component),它不会渲染为实际的 DOM 节点,而是包裹其下的子组件实例,控制它们是销毁还是缓存。默认情况下,当一个被 <keep-alive> 包裹的组件从视图移除时,不会立即销毁组件实例,而是将其实例保留在内存中;当再次切换回来时,会继续复用之前的实例,保留先前的状态。

2.1 缓存流程示意图

以下使用简化的 ASCII 图示,帮助理解组件挂载与缓存的过程:

┌──────────────┐      切换视图       ┌──────────────┐
│ 组件 A 挂载    │ -----------------> │ 组件 A 缓存    │
│ (mounted)     │ <----------------- │ (cached,无销毁)│
│               │      再次显示      │               │
└──────────────┘                    └──────────────┘

┌──────────────┐      切换视图       ┌──────────────┐
│ 组件 B 挂载    │ -----------------> │ 组件 B 缓存    │
│ (mounted)     │ <----------------- │ (cached,无销毁)│
│               │      再次显示      │               │
└──────────────┘                    └──────────────┘

当未使用 <keep-alive> 时,组件 A 和组件 B 互相切换就会被销毁再重新挂载(destroyedcreatedmounted)。而包裹在 <keep-alive> 下的组件,则是第一次挂载后进入“缓存”,再次切换回来时直接复用,不重复走创建与销毁流程。


三、基础用法与示例

3.1 动态组件方式

最常见的场景是结合 <component :is="…"> 动态组件使用 <keep-alive>。例如:在页面中,通过按钮或 Tabs 切换不同组件:

<template>
  <div>
    <!-- 切换按钮 -->
    <button @click="active = 'CompA'">显示 A</button>
    <button @click="active = 'CompB'">显示 B</button>

    <!-- 将动态组件包裹在 keep-alive -->
    <keep-alive>
      <component :is="active" />
    </keep-alive>
  </div>
</template>

<script>
import CompA from './CompA.vue'
import CompB from './CompB.vue'

export default {
  components: { CompA, CompB },
  data() {
    return {
      active: 'CompA' // 初始显示 CompA
    }
  }
}
</script>
  • 启动时active='CompA',Vue 创建并挂载 CompA,同时保留其实例。
  • 切换到 CompBCompA 被移动出视图,但因为被 keep-alive 包裹,实际并不销毁,而是进入缓存;随后创建并挂载 CompB,同理也缓存。
  • 再次切换到 CompA:直接复用缓存的 CompA 实例,不重新执行 createdmounted

3.2 路由组件方式

在 Vue-Router 场景下,经常把 <router-view> 放入 <keep-alive>,为指定的路由组件实现缓存:

<template>
  <div>
    <!-- 仅缓存以下两个路由的组件 -->
    <keep-alive include="Home,About">
      <router-view />
    </keep-alive>
  </div>
</template>
  • 当路由切换到 /home 时挂载 Home.vue;切换到 /about 时挂载 About.vue。两者都会被缓存下来。
  • 切换到 /contact 时,Contact.vue 不在 include 列表内,会正常销毁,不会缓存。
注意:要使路由组件被 keep-alive 缓存,需要在组件上设置 name,如 export default { name: 'Home', … }

四、常用属性

<keep-alive> 提供了几个可选属性,用于精细控制哪些组件需要缓存、缓存上限等。

4.1 includeexclude

  • include:一个字符串或正则表达式,只有名称匹配 include 的组件才会被缓存。
  • exclude:与 include 相反,名称匹配 exclude 的组件不会被缓存。
优先级:如果同时使用 includeexcludeexclude 优先级更高,即先判断是否在排除列表内,如果命中则不缓存。
<keep-alive include="CompA,CompB" exclude="CompC">
  <component :is="active" />
</keep-alive>
  • 只有 CompACompB 会缓存;
  • CompC 则无论是否出现在 include 中,都会被排除,不缓存。

可以使用正则表达式来匹配多个组件名称:

<!-- 缓存所有名称以 Page 开头的组件 -->
<keep-alive include="/^Page.*/">
  <component :is="active" />
</keep-alive>

4.2 max

  • max:指定缓存大小上限(组件实例的最大数量)。当缓存数量超出 max 时,最早被缓存(最久未访问)的组件会被销毁并淘汰。
<keep-alive max="2">
  <component :is="active" />
</keep-alive>
  • 假设先后切换组件为 A → B → C,此时缓存中有 A, B, C 三个实例,超过了 max=2,那么最先缓存的 A 会被销毁,缓存只保留 B, C

五、生命周期钩子

当组件被 keep-alive 缓存时,会触发两个额外的生命周期钩子:activateddeactivated。它们用于替代普通组件的 mounteddestroyed 来进行针对“缓存/复用”场景下的初始化/清理操作。

  • activated:当组件被激活(从缓存中恢复)时调用。
  • deactivated:当组件被停用(移动到缓存中,但未销毁)时调用。
<!-- Example.vue -->
<template>
  <div>
    <p>示例组件。当前计数:{{ count }}</p>
    <button @click="count++">递增</button>
  </div>
</template>

<script>
export default {
  name: 'Example',
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    console.log('mounted:组件初次挂载')
  },
  activated() {
    console.log('activated:组件从缓存中恢复')
  },
  deactivated() {
    console.log('deactivated:组件被缓存')
  },
  beforeDestroy() {
    console.log('beforeDestroy:组件销毁前')
  },
  destroyed() {
    console.log('destroyed:组件真正销毁')
  }
}
</script>
  • 第一次 active='Example' 时,先执行 created -> mounted
  • 切换到其他组件,此时触发 deactivated(而非 beforeDestroydestroyed)。
  • 再次切换回 Example,执行 activated
  • 如果因 maxexclude 等原因被真正销毁,则 beforeDestroy → destroyed 会被调用。

六、详细图解:keep-alive 缓存流程

下面用一张更详细的 ASCII 流程图演示 keep-alive 对组件的挂载、缓存、激活与销毁过程。

┌─────────────────────────────────────────────────────────────────┐
│                      初次渲染(active = A)                     │
│                                                                 │
│  <keep-alive>                                                    │
│    <component is="A" />  →  Vue 真正执行 new A() 实例化,并挂载     │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.created                                                   │
│   2. A.mounted                                                   │
│   3. 缓存池:{ A 实例 }                                           │
└─────────────────────────────────────────────────────────────────┘

                     切换到 B (active = B)
┌─────────────────────────────────────────────────────────────────┐
│                      缓存 A,并挂载 B                              │
│                                                                 │
│  <keep-alive>                                                    │
│    A 实例  (状态:cached, 调用 A.deactivated)                      │
│    <component is="B" />  →  new B()                                │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.deactivated                                               │
│   2. B.created                                                   │
│   3. B.mounted                                                   │
│   4. 缓存池:{ A 实例, B 实例 }                                   │
└─────────────────────────────────────────────────────────────────┘

                     再次切换到 A (active = A)
┌─────────────────────────────────────────────────────────────────┐
│                     从缓存中恢复 A,并停用 B                        │
│                                                                 │
│  <keep-alive>                                                    │
│    <component is="A" />  →  复用 A 实例,调用 A.activated           │
│    B 实例  (调用 B.deactivated)                                   │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. B.deactivated                                               │
│   2. A.activated                                                 │
│   3. 缓存池:{ A 实例, B 实例 }                                   │
└─────────────────────────────────────────────────────────────────┘

                     缓存数超出 max=1
┌─────────────────────────────────────────────────────────────────┐
│           假设 max=1,缓存池已有 A, B,此时挂载 C (active = C)       │
│                                                                 │
│  <keep-alive max="1">                                             │
│    A 实例 → 淘汰最早缓存 A(调用 A.beforeDestroy、A.destroyed)     │
│    B 实例 → 调用 B.deactivated                                    │
│    <component is="C" />  →  new C()                                │
│  </keep-alive>                                                    │
│                                                                 │
│ 过程:                                                            │
│   1. A.beforeDestroy                                            │
│   2. A.destroyed                                                 │
│   3. B.deactivated                                               │
│   4. C.created                                                   │
│   5. C.mounted                                                   │
│   6. 缓存池:{ B 实例, C 实例 } (A 被彻底销毁)                   │
└─────────────────────────────────────────────────────────────────┘

要点

  1. 首次挂载:组件走 createdmounted,并进入缓存池。
  2. 切换非当前组件:停用组件,走 deactivated,保留在缓存池。
  3. 再次回到缓存组件:直接复用,不重复创建,走 activated
  4. 缓存数超限:最久未访问(最先缓存)的组件会被淘汰,走 beforeDestroydestroyed

七、典型应用场景

7.1 页面/标签切换时保持状态

在多标签、Tab 或侧边栏导航的场景中,希望返回到先前的页面时,仍能保持之前的滚动位置、表单填写状态或页面数据。示例如下:

<template>
  <div>
    <!-- 顶部 Tabs -->
    <el-tabs v-model="activeTab">
      <el-tab-pane label="用户列表" name="UserList" />
      <el-tab-pane label="设置中心" name="Settings" />
    </el-tabs>

    <!-- 用 keep-alive 缓存选项卡组件 -->
    <keep-alive>
      <component :is="activeTab" />
    </keep-alive>
  </div>
</template>

<script>
import UserList from './UserList.vue'
import Settings from './Settings.vue'

export default {
  components: { UserList, Settings },
  data() {
    return {
      activeTab: 'UserList'
    }
  }
}
</script>
  • 效果:在 UserListSettings 之间切换时,UserList 组件的数据与滚动位置会保持;无需重新请求接口或重新渲染表格数据。

7.2 路由页面缓存(路由复用)

对于较大型的 SPA 应用,某些页面切换成本较高(如需要重新请求接口、重新初始化图表等),借助 <keep-alive> 可以在返回时快速响应。以 Vue-Router 为例:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Dashboard from '@/views/Dashboard.vue'
import Profile from '@/views/Profile.vue'

const routes = [
  { path: '/', component: Home, name: 'Home' },
  { path: '/dashboard', component: Dashboard, name: 'Dashboard' },
  { path: '/profile', component: Profile, name: 'Profile' }
]

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

export default router
<!-- App.vue -->
<template>
  <div id="app">
    <router-link to="/">Home</router-link>
    <router-link to="/dashboard">Dashboard</router-link>
    <router-link to="/profile">Profile</router-link>

    <!-- 仅缓存 Dashboard 和 Profile 页面 -->
    <keep-alive include="Dashboard,Profile">
      <router-view />
    </keep-alive>
  </div>
</template>
  • 在从 Dashboard 跳转到 Home,再返回时,Dashboard 依然保持之前的状态(如图表滚动、过滤条件等);
  • Home 并未被缓存,每次进入都会重新渲染。

7.3 表单/富文本编辑状态保持

在多步骤表单或富文本编辑场景下,用户填写到一半切换到其他页面,再返回时希望输入内容不丢失:

<!-- FormStep1.vue -->
<template>
  <div>
    <h3>第一步:填写个人信息</h3>
    <el-form :model="form">
      <el-form-item label="姓名">
        <el-input v-model="form.name" />
      </el-form-item>
      <!-- 更多表单项 -->
    </el-form>
  </div>
</template>
<script>
export default {
  name: 'FormStep1',
  data() {
    return {
      form: {
        name: '',
        age: null
      }
    }
  },
  activated() {
    console.log('表单组件被恢复,保留之前输入')
  },
  deactivated() {
    console.log('表单组件被切换,缓存数据')
  }
}
</script>
<!-- FormStepContainer.vue -->
<template>
  <div>
    <el-steps :active="currentStep">
      <el-step title="个人信息" />
      <el-step title="联系方式" />
      <el-step title="提交" />
    </el-steps>
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
    <div>
      <el-button @click="prev" :disabled="currentStep === 0">上一步</el-button>
      <el-button @click="next" :disabled="currentStep === steps.length - 1">下一步</el-button>
    </div>
  </div>
</template>

<script>
import FormStep1 from './FormStep1.vue'
import FormStep2 from './FormStep2.vue'
import FormStep3 from './FormStep3.vue'

export default {
  components: { FormStep1, FormStep2, FormStep3 },
  data() {
    return {
      steps: ['FormStep1', 'FormStep2', 'FormStep3'],
      currentStep: 0
    }
  },
  computed: {
    currentComponent() {
      return this.steps[this.currentStep]
    }
  },
  methods: {
    next() {
      if (this.currentStep < this.steps.length - 1) {
        this.currentStep++
      }
    },
    prev() {
      if (this.currentStep > 0) {
        this.currentStep--
      }
    }
  }
}
</script>
  • 使用 <keep-alive> 包裹三个步骤的组件,可以保证在切换时,表单数据和用户输入不丢失。
  • 在组件的 activateddeactivated 钩子中,可以根据需要做二次校验、重置焦点等操作。

7.4 性能优化:避免重复渲染

在一些数据量较大的页面(如后台列表、大规模图表、三维可视化)中,重复销毁与创建组件特别浪费资源。可以借助 keep-alive 缓存视图,提升切换速度与用户体验。

<template>
  <div class="dashboard-container">
    <el-menu v-model="activeMenu" @select="onSelect">
      <el-menu-item index="analytics">Analytics</el-menu-item>
      <el-menu-item index="reports">Reports</el-menu-item>
    </el-menu>
    <keep-alive>
      <component :is="currentView" />
    </keep-alive>
  </div>
</template>

<script>
import Analytics from './Analytics.vue'
import Reports from './Reports.vue'

export default {
  components: { Analytics, Reports },
  data() {
    return {
      activeMenu: 'analytics'
    }
  },
  computed: {
    currentView() {
      return this.activeMenu === 'analytics' ? 'Analytics' : 'Reports'
    }
  },
  methods: {
    onSelect(menu) {
      this.activeMenu = menu
    }
  }
}
</script>
  • 节省网络与数据请求Analytics.vue 可能会花费几秒钟加载大型图表,缓存后再次切换回来会立即显示。
  • 避免销毁重建 DOM:大规模 DOM 重建会引起卡顿,keep-alive 可使组件保留在内存中,保持绘图状态。

八、进阶应用

8.1 动态控制缓存

在某些场景下,需要根据业务逻辑动态决定是否缓存。例如:当用户选中了“开启缓存”复选框时,才缓存组件;否则每次都销毁重建。

<template>
  <div>
    <el-checkbox v-model="useCache">开启视图缓存</el-checkbox>
    <component :is="useCache ? 'keep-alive' : 'div'">
      <component :is="currentTab" />
    </component>
  </div>
</template>

<script>
import TabA from './TabA.vue'
import TabB from './TabB.vue'

export default {
  components: { TabA, TabB },
  data() {
    return {
      currentTab: 'TabA',
      useCache: true
    }
  }
}
</script>
  • useCache = true 时,<component is="keep-alive"> 等价于 <keep-alive>,会缓存 TabA/TabB
  • useCache = false 时,<component is="div"> 仅是一个 div 容器,不会触发缓存逻辑,每次切换都销毁重建。

8.2 手动清除缓存

Vue2 中可以通过 <keep-alive :include> 或动态修改 key 来控制缓存。Vue3 提供了 <KeepAlive> 对应的 JavaScript API,例如 $refs.keepAliveComponent && $refs.keepAliveComponent.cache 等(内部缓存对象),但官方并不推荐通过私有 API 操作。

更安全的方式是,通过改变组件名或修改 include 列表,让原缓存失效。例如:将组件 name 改为动态值,迫使 keep-alive 认为是新组件,重新挂载并丢弃旧缓存。

<!-- 通过唯一标识作为 name,让 keep-alive 每次都认为是新组件 -->
<keep-alive>
  <component :is="currentComponent" :key="cacheKey" />
</keep-alive>

<!-- 当需要清空缓存时,只需修改 cacheKey -->
<script>
export default {
  data() {
    return {
      currentComponent: 'MyComp',
      cacheKey: 'v1'
    }
  },
  methods: {
    clearCache() {
      this.cacheKey = 'v' + Date.now()  // 随机更新 key,清空旧缓存
    }
  }
}
</script>

九、完整示例:综合应用

下面展示一个稍微完整些的示例——基于 Vue-Router 的多页面应用,演示如何使用 keep-alive 缓存部分路由并结合生命周期钩子做额外处理。

9.1 项目目录结构

src/
├─ App.vue
├─ main.js
└─ views/
   ├─ Home.vue
   ├─ Dashboard.vue
   └─ Profile.vue
router/
└─ index.js

9.2 路由配置(router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Dashboard from '@/views/Dashboard.vue'
import Profile from '@/views/Profile.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/dashboard', name: 'Dashboard', component: Dashboard },
  { path: '/profile', name: 'Profile', component: Profile }
]

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

export default router

9.3 根组件(App.vue

<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/dashboard">Dashboard</router-link> |
      <router-link to="/profile">Profile</router-link>
    </nav>
    <!-- 只缓存 Dashboard 和 Profile -->
    <keep-alive include="Dashboard,Profile">
      <router-view />
    </keep-alive>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
  • include="Dashboard,Profile":只有名称为 DashboardProfile 的组件会被缓存。
  • Home 每次切换到该路由都会重新渲染。

9.4 页面组件示例

9.4.1 Dashboard.vue

<template>
  <div>
    <h2>Dashboard</h2>
    <p>这是一个会被缓存的页面。</p>
    <p>当前时间:{{ now }}</p>
    <button @click="showCount++">增加计数 ({{ showCount }})</button>
  </div>
</template>

<script>
export default {
  name: 'Dashboard',
  data() {
    return {
      now: new Date().toLocaleTimeString(),
      showCount: 0,
      timer: null
    }
  },
  mounted() {
    this.startTimer()
    console.log('Dashboard mounted')
  },
  activated() {
    this.startTimer()
    console.log('Dashboard activated')
  },
  deactivated() {
    clearInterval(this.timer)
    console.log('Dashboard deactivated')
  },
  beforeDestroy() {
    console.log('Dashboard beforeDestroy')
  },
  destroyed() {
    console.log('Dashboard destroyed')
  },
  methods: {
    startTimer() {
      this.timer = setInterval(() => {
        this.now = new Date().toLocaleTimeString()
      }, 1000)
    }
  }
}
</script>
  • 首次挂载从缓存恢复 时,mountedactivated 都会被调用,此处都启动定时器更新时间。
  • 当切换到其他路由时,走 deactivated,清除定时器,避免内存泄漏。
  • 如果组件因某些原因被销毁(如缓存淘汰),会触发 beforeDestroydestroyed

9.4.2 Profile.vue

<template>
  <div>
    <h2>Profile</h2>
    <el-form :model="form">
      <el-form-item label="用户名">
        <el-input v-model="form.username" />
      </el-form-item>
      <el-form-item label="邮箱">
        <el-input v-model="form.email" />
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: 'Profile',
  data() {
    return {
      form: {
        username: '',
        email: ''
      }
    }
  },
  activated() {
    console.log('Profile 组件被激活,保留之前的表单数据')
  },
  deactivated() {
    console.log('Profile 组件被停用,表单数据仍保留在缓存中')
  }
}
</script>
  • 即使从 Profile 跳转到别的页面,返回时能看到先前填写的表单内容。

9.4.3 Home.vue

<template>
  <div>
    <h2>Home</h2>
    <p>这是一个不被缓存的页面,每次都会重新加载。</p>
    <button @click="count += 1">计数 ({{ count }})</button>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    console.log('Home mounted,count 重置为 0')
  }
}
</script>
  • Home 因未在 include 中,被每次切换时都会重新创建与销毁,count 始终从 0 开始。

十、小结与注意事项

  1. 缓存的本质<keep-alive> 并不会生成额外的 DOM 节点,而是将组件实例缓存在内存中。
  2. 组件 name 必须唯一:要让 include/exclude 生效,需要给每个组件设置唯一的 name 属性。
  3. 合理设置 max:根据应用场景与内存开销,控制缓存大小,防止内存占用过高。
  4. 正确清理定时器与订阅:在 deactivated 钩子中清理 setIntervalsetTimeout、WebSocket、订阅等资源,避免内存泄漏。
  5. 注意 props 或 dynamic key 的变化:当组件的 key 发生改变时,会被视为全新组件,先前缓存会失效并销毁。可用作手动清缓存手段。
  6. 服务端渲染(SSR)下不支持缓存:Vue SSR 无法在服务端缓存组件实例,keep-alive 仅在客户端有效。

通过本文,你应当对 keep-alive 的原理、属性、生命周期钩子以及典型应用场景有了系统的理解。熟练运用 keep-alive,能让你的 Vue 应用在性能与用户体验上更加出色。祝你学习顺利!

VUE
最后修改于:2025年05月28日 13:26

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日