十四、KeepAlive
一、KeepAlive1. KeepAlive 是什么KeepAlive是 Vue 3 提供的一个内置抽象组件。它的核心作用就是缓存不活动的组件实例而不是销毁它们。当组件被KeepAlive包裹后切换出去时不会被销毁其状态会被保留在内存中再次切换回来时组件会直接从缓存中激活无需重新创建和挂载。2. 使用场景在实际开发中我们通常在以下场景使用它保留表单状态用户在一个多步骤的长表单里填了一半不小心点了别的菜单再返回时希望之前填的内容还在不用重填。保留列表滚动位置比如电商 App 的商品列表页用户往下滑看了几个商品点进详情页后又返回希望列表还在刚才滚动到的位置而不是回到顶部。减少重复请求从列表页进入详情页再返回因为页面被缓存了就不需要重新发起网络请求去拉取列表数据提升了页面切换速度和用户体验。二、属性KeepAlive支持以下三个属性用于精细控制缓存行为参数解释简短举例include白名单。指定哪些组件需要被缓存。:include[UserList](只缓存用户列表页)exclude黑名单。指定哪些组件不要被缓存。:exclude[LoginPage](除了登录页其他都缓存)max最大数量。限制最多缓存多少个实例超出时自动销毁最久没访问的防止内存溢出。:max10(最多保留10个页面缓存)include和exclude支持三种传参方式你可以灵活组合逗号分隔的字符串includea,b不需要v-bind适合固定写死的一两个正则表达式:include/List|Detail/适合批量匹配数组最推荐:include[Dashboard, /List/]数组里甚至可以同时放字符串和正则三、常见用法1. 用法一结合动态组件template !-- 非活跃的组件将会被缓存 -- KeepAlive component :isactiveComponent / /KeepAlive /template当activeComponent的值切换时之前的组件实例不会被销毁而是被缓存起来下次切回来时可以立即恢复。2. 用法二结合 Vue Router实际开发中最常见在 Vue Router 中我们通常这样包裹router-view并配合include属性template router-view v-slot{ Component } !-- 只缓存名为 UserList 和 Settings 的组件且最多缓存 10 个 -- KeepAlive :include[UserList, Settings] :max10 component :isComponent / /KeepAlive /router-view /template这种方式是实际项目中最常见的用法可以实现页面级别的缓存控制让用户在页面间来回切换时获得流畅的体验。四、专属生命周期钩子被KeepAlive缓存的组件在切走时不会触发unmounted卸载切回来时也不会触发mounted挂载。Vue 为它提供了两个专属钩子onActivated组件被激活时调用首次挂载以及每次从缓存中拿出来时。onDeactivated组件被停用/缓存时调用从 DOM 移除进入缓存或者组件被彻底卸载时。script setup import { onActivated, onDeactivated } from vue onActivated(() { console.log(我被激活啦可以在这里重新拉取最新数据) }) onDeactivated(() { console.log(我被缓存起来啦可以在这里清除定时器节省性能) }) /script合理利用这两个钩子可以在组件激活时刷新数据在停用时清理定时器或释放资源让应用性能更优。五、KeepAlive避坑指南️ 坑 1include和exclude没生效最常见症状你明明写了:include[UserList]但发现UserList组件每次切换还是被销毁重建了缓存根本没生效。原因include和exclude是根据组件的name选项来匹配的。如果你的组件没有显式定义name或者名字拼错了它就会匹配失败。如果你用的是 Vue 3 的script setup必须使用defineOptions显式声明名字script setup // 确保这里的名字和 include 里写的完全一致区分大小写 defineOptions({ name: UserList }) /script️ 坑 2切回页面时数据没有刷新“幽灵数据”症状你在商品列表页搜索了苹果点进详情页后别人刚好上架了一批香蕉。你退回列表页发现列表里还是只有苹果没有最新的香蕉。原因因为组件被缓存了切回来时不会触发created或mounted钩子所以你的数据请求函数根本没执行。解决方案把需要每次进入页面都执行的逻辑比如拉取最新数据放到onActivated钩子中script setup import { onActivated } from vue onActivated(() { console.log(页面被激活重新拉取最新列表数据) fetchGoodsList() }) /script️ 坑 3内存泄漏App 越用越卡症状用户在列表和几十个详情页之间疯狂跳转过了半小时手机发烫浏览器直接崩溃。原因你使用了KeepAlive但没有加max限制。每一个访问过的页面都被永久保存在内存里最终导致内存溢出。解决方案永远给KeepAlive加上max属性让它采用LRU最近最少使用策略自动清理旧缓存。!-- 移动端建议 5~10PC端建议 10~20 -- KeepAlive :max10 router-view / /KeepAlive️ 坑 4定时器/事件监听器没有清除症状你在列表页写了一个每秒执行一次的倒计时切到别的页面后虽然页面看不见了但控制台还在疯狂打印甚至切回来后出现了多个倒计时在跑。原因缓存组件时组件并没有被销毁普通的unmounted钩子不会触发定时器自然也没被清除。解决方案在onDeactivated中暂停/清除定时器在onActivated中重新启动script setup import { onActivated, onDeactivated } from vue let timer null onActivated(() { timer setInterval(() console.log(滴答), 1000) }) onDeactivated(() { clearInterval(timer) // 离开时务必清理 }) /script️ 坑 5表单数据串台了动态路由复用症状你访问/user/1填了一些表单没保存直接访问/user/2结果发现表单里还是刚才用户 1 的数据原因/user/1和/user/2使用的是同一个组件比如UserDetail.vueKeepAlive认为它们是同一个组件直接复用了缓存。解决方案对于这种需要根据 ID 动态变化的页面千万不要全局缓存它要么把它加入exclude黑名单。要么在onActivated中判断 ID 是否变化如果变了就手动重置表单数据script setup import { onActivated } from vue import { useRoute } from vue-router const route useRoute() const lastId ref() onActivated(() { const currentId route.params.id if (currentId ! lastId.value) { resetForm() // ID变了清空表单 lastId.value currentId } }) /script六、企业级路由缓存方案在实际的中后台系统中页面往往有几十上百个。如果我们在KeepAlive里写死include[A, B, C]那维护起来绝对是灾难。企业级的标配做法是把缓存的决定权交给路由配置meta然后让KeepAlive自动去读取。第一步在路由配置中打上标记meta我们可以在vue-router的路由配置中利用meta元信息字段来标记哪些页面需要缓存。// router/index.jsconstroutes[{path:/user-list,name:UserList,component:()import(/views/UserList.vue),meta:{title:用户列表,keepAlive:true// ⭐ 打上标记这个页面需要缓存}},{path:/login,name:Login,component:()import(/views/Login.vue),meta:{title:登录,keepAlive:false// ⭐ 打上标记这个页面不需要缓存}}]这样一来缓存的控制权就从组件内部转移到了路由配置层每一页是否缓存一目了然便于统一管理。第二步在布局组件中动态生成include列表在包裹router-view的父级组件通常是App.vue或Layout.vue中我们不需要写死数组而是通过遍历当前路由表把带有keepAlive: true的组件名动态提取出来。!-- Layout.vue -- template router-view v-slot{ Component } !-- 动态绑定 computed 计算出来的缓存列表 -- KeepAlive :includecachedViews component :isComponent / /KeepAlive /router-view /template script setup import { computed } from vue import { useRouter } from vue-router const router useRouter() // ⭐ 核心魔法动态计算需要缓存的组件名 const cachedViews computed(() { return router.getRoutes() .filter(route route.meta?.keepAlive) // 过滤出 meta.keepAlive 为 true 的路由 .map(route route.name) // 提取出路由的 name }) /script这种写法的好处是以后你新增一个列表页只需要在路由配置里加一行keepAlive: true缓存就自动生效了根本不需要去改KeepAlive的代码实现了“配置即缓存”的优雅解耦。第三步企业级进阶 —— 动态清理缓存Pinia/Vuex刚才的写法解决了静态配置的问题但实际业务中缓存是动态变化的。场景你在新建用户页面提交成功后再返回用户列表页这时候列表必须刷新不能再读旧缓存了这时候就需要引入状态管理以Pinia为例让缓存活起来1. 创建一个缓存 Store// stores/cache.jsimport{defineStore}frompiniaimport{ref}fromvueexportconstuseCacheStoredefineStore(cache,(){constcachedViewsref([])// 添加缓存constaddCachedView(viewName){if(!cachedViews.value.includes(viewName)){cachedViews.value.push(viewName)}}// ⭐ 删除缓存关键constremoveCachedView(viewName){constindexcachedViews.value.indexOf(viewName)if(index-1){cachedViews.value.splice(index,1)}}return{cachedViews,addCachedView,removeCachedView}})2. 在组件中动态销毁缓存!-- NewUser.vue (新建用户页面) -- script setup import { useCacheStore } from /stores/cache import { useRouter } from vue-router const cacheStore useCacheStore() const router useRouter() const handleSubmit async () { await saveUser() // 保存数据 // ⭐ 提交成功后立刻把列表页的缓存干掉 cacheStore.removeCachedView(UserList) // 然后跳回列表页此时列表页会重新创建并拉取最新数据 router.push(/user-list) } /script通过这种“路由 meta 标记 动态 include Pinia 动态清理”的组合拳你就能在大型项目中优雅地管理所有页面缓存既兼顾了用户体验又避免了内存泄漏和数据陈旧的问题。