在 Vue 的开发过程中,页面切换、组件复用是一种非常常见的创建。为了避免每次切换都重新销毁组件从而浪费性能,Vue 提供了一个神奇的内置组件:<keep-alive>。
而为了配合它对组件进行“缓存式挂起和激活”的能力,Vue 提供了两个关键的生命周期钩子函数:onActivated 和 onDeactivated。
这两个组合功能,既是性能优化的利器,也是组件状态管理的核心部分。
1. 什么是 <keep-alive>?
1.1 基本介绍
<keep-alive> 是 Vue 提供的内置组件,用来缓存不活动的组件实例,而不是销毁它们。
当在页面中切换组件时,如果不使用 keep-alive,Vue 会把旧组件销毁,再创建新组件。而加上 keep-alive 后,旧组件会被缓存起来而不是销毁,下次再切换回来的时候会直接激活而不是重新创建。
1.2 基本用法
<template>
<keep-alive>
<component :is="currentView" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue'
const currentView = ref('Home')
</script>
currentView 控制当前显示哪个组件,<keep-alive> 会缓存之前展示过的组件实例。
1.3 额外用法
1、include / exclude 筛选组件缓存
<keep-alive include="Home,About">
<router-view />
</keep-alive>
也可以使用正则表达式:
<keep-alive :include="/^Home|About$/">
还可以动态绑定:
<keep-alive :include="cachedComponents">
<router-view />
</keep-alive>
<script setup>
const cachedComponents = ref(['Home', 'About'])
</script>
2、max:控制最多缓存多少个组件
<keep-alive :max="3">
当使用 max 属性时,Vue 内部会使用一个 LRU 算法,维持缓存组件的使用顺序:
- 每次激活一个组件,放到链表头
- 超出缓存数量,移除链表尾的组件(最久没使用的)
2. keep-alive 是如何工作的?
2.1 缓存机制
当一个组件被 <keep-alive> 包裹,它第一次渲染会被正常挂载,但是在切换离开时,它不会被销毁,而是会进入“缓存池”,等到再次被显示时,会从缓存中取出这个组件实例,而不是重新创建。
2.2 生命周期的变化
默认组件的生命周期:
beforeCreate -> created -> beforeMount -> mounted -> beforeUnmount -> unmounted
但被 keep-alive 包裹的组件,在第一次渲染后,会有不同的行为:
beforeCreate -> created -> beforeMount -> mounted
切换出去:
-> deactivated
切换回来:
-> activated
也就是说,组件并没有被销毁,只是被“挂起”了!
3. onActivated 钩子函数
3.1 介绍
onActivated 是 Vue 3 中的 Composition API 生命周期钩子函数,用于监听被 <keep-alive> 缓存的组件重新激活时的行为。
3.2 使用场景
- 页面切换回来后需要重新发请求
- 需要手动刷新 DOM 或重新获取状态
- 需要恢复滚动位置
- 实时数据重新拉取
举个 🌰
<!-- App.vue -->
<template>
<keep-alive>
<UserInfo v-if="activeTab === 'user'" />
</keep-alive>
<Settings v-if="activeTab === 'settings'" />
</template>
<script setup>
import { ref } from 'vue'
import UserInfo from './UserInfo.vue'
import Settings from './Settings.vue'
const activeTab = ref('user')
</script>
<!-- UserInfo.vue -->
<template>
<div>
<h2>用户信息</h2>
<p>{{ userInfo }}</p>
</div>
</template>
<script setup>
import { ref, onActivated, onMounted } from 'vue'
const userInfo = ref('')
function fetchUserInfo() {
console.log('请求用户数据...')
userInfo.value = '用户:张三,年龄:28'
}
onMounted(() => {
fetchUserInfo() // 首次加载
})
onActivated(() => {
fetchUserInfo() // 被 keep-alive 缓存后再次激活
})
</script>
3.3 onDeactivated 简要说明(配合使用)
onDeactivated 会在组件被缓存(切换走)时触发。
onDeactivated(() => {
console.log('组件已被缓存,不再活跃')
})
比如:可以在 onDeactivated 中保存滚动条位置、清理定时器等。
4. keep-alive 与 onActivated 关系
特性 | <keep-alive> | onActivated |
---|---|---|
类型 | 内置组件 | 生命周期钩子 |
功能 | 缓存组件 | 组件被重新激活时执行 |
使用方式 | 包裹组件 | 在组件内部调用 |
生命周期影响 | 改变组件销毁行为 | 替代 mounted 在激活时执行逻辑 |
配合使用 | YES | YES |
keep-alive 负责不销毁组件,onActivated 负责组件被重新激活后重新运行逻辑。二者是被动缓存(keep-alive)+ 主动唤醒(onActivated)的组合。
5. 具体实战:结合路由缓存组件页面
构建一个路由切换 + 缓存页面的案例:
路由配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{ path: '/', component: Home, name: 'Home' },
{ path: '/about', component: About, name: 'About' },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
App.vue 结构
<template>
<div>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<keep-alive>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</keep-alive>
</div>
</template>
注意:
如果 <router-view> 里没用 v-slot 解构 Component,那么 <keep-alive> 不会起作用。Vue Router 的 <router-view> 是动态组件,必须用 component :is="xxx" 配合。
About.vue
<template>
<div>
<h1>关于我们</h1>
<p>{{ time }}</p>
</div>
</template>
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'
const time = ref('')
function updateTime() {
time.value = new Date().toLocaleTimeString()
}
onActivated(() => {
console.log('组件重新激活')
updateTime()
})
onDeactivated(() => {
console.log('组件缓存中')
})
</script>
页面不会重新加载,而是调用了 onActivated 更新时间。
6. 常见坑
1、onMounted 不会在组件被激活时再次触发
必须使用 onActivated!
// 错误:不会再触发
onMounted(() => {
console.log('只执行一次')
})
2、组件名必须准确写入 include 才会被缓存
确保设置了组件名:
<script>
export default {
name: 'Home'
}
</script>
或者 defineOptions:
defineOptions({ name: 'Home' })
3、setup 代码不会重复执行
setup() 只执行一次。不要在里面写需要“每次切换回来都更新”的逻辑,要写在 onActivated 里!
7. keep-alive 的内部工作机制
本质:基于组件的“缓存池”机制。
Vue 在创建组件时,会检查它是否被 keep-alive 包裹。若是,它就不会在卸载组件时将其销毁,而是:
- 把组件实例保留在内存中(缓存池 cache)
- 等再次渲染该组件时,直接复用缓存,而不是重新挂载
7.1 工作原理 & 流程图
↓
<keep-alive> 包裹组件
↓
检查组件的 name(或 key)
↓
是否已在缓存?
↙ ↘
是 否
直接复用 正常创建组件实例
(调用 activate) ↓
↑ 存入缓存 cache
└─────<─┐
│
组件卸载时,拦截 unmount
│
不销毁,而是调用 deactivate
7.2 核心机制
keep-alive 实际上会维护一个组件缓存池:
const cache = new Map() // 用于缓存组件 vnode
// 渲染时
if (cache.has(name)) {
// 从缓存中取出 vnode
vnode = cache.get(name)
activate(vnode) // 激活缓存中的实例
} else {
// 正常创建并挂载组件
vnode = createComponent(...)
cache.set(name, vnode) // 缓存起来
}
卸载时拦截(关键点):
// 正常组件卸载时会 unmount(销毁)
// 被 keep-alive 包裹的组件会触发 deactivate
与生命周期的关系
keep-alive 改变了组件的生命周期!
生命周期钩子 | 意义 | 何时触发 |
---|---|---|
mounted | 挂载 | 首次创建组件时 |
unmounted | 卸载 | 不再显示,未被缓存 |
activated | 激活 | 缓存中的组件重新显示 |
deactivated | 缓存离开 | 缓存组件离开视图 |
7.3 性能优化建议
场景 | 建议 |
---|---|
大量 tab 切换页面 | 使用 include 精准控制缓存组件 |
表单输入保留 | 使用 keep-alive 避免重置数据 |
缓存过多 | 设置 max 限制缓存数量 |
复杂嵌套组件 | 谨慎使用嵌套 keep-alive,避免难调试 |
7.4 源码浅析
在 runtime-core 中可以找到 KeepAliveImpl:
const KeepAliveImpl = {
name: `KeepAlive`,
setup(props, { slots }) {
const cache = new Map()
const keys = new Set()
const instance = getCurrentInstance()
const sharedContext = instance.ctx
return () => {
const vnode = slots.default()
const name = getComponentName(vnode.type)
if (cache.has(name)) {
// 命中缓存
vnode.component = cache.get(name)
// 激活缓存
activate(vnode)
} else {
// 创建并缓存
cache.set(name, vnode)
}
return vnode
}
}
}
最后:
<keep-alive> 和 onActivated 是 Vue 性能优化的王牌组合。它们解决了组件频繁销毁重建的性能问题,并提供了灵活的生命周期钩子机制精准控制组件行为。