Code前端首页关于Code前端联系我们

Vue3 Keep-Alive怎么用才顺手?新手避坑+新特性+场景实战全指南

terry 5小时前 阅读数 78 #Vue
文章标签 AliveAlive避坑实战

最近刷项目代码时,总能碰到同学说Vue3的Keep-Alive用起来和Vue2不一样,要么缓存不生效,要么缓存的数据串了,要么死活不知道怎么按需清理,其实只要搞懂它的核心变化、避点小雷区,再结合具体场景练手,Keep-Alive绝对是提升单页应用体验的利器,今天咱们就好好唠唠。

Vue3里的Keep-Alive和Vue2有啥本质区别?

很多人刚转Vue3,顺手就把Vue2的包裹方式搬过来,结果发现报错,最直观的区别就是包裹方式变了——Vue3不再支持直接在路由出口、插槽外层套<keep-alive>组件,必须配合Vue3内置的<component :is="...">动态组件或者<router-view>的v-slot(注意不是原来的直接传include/exclude)。

举个最简单的对比例子:Vue2里是直接写<keep-alive><router-view /></keep-alive>,Vue3得改成<router-view v-slot="{ Component, route }"><keep-alive><component :is="Component" /></keep-alive></router-view>,这里的v-slot解构出的Component就是路由对应的渲染组件,route是当前路由信息,后续做include/exclude的动态筛选、max队列清理的监控都要用到它。

除了包裹方式,Vue3还把include/exclude/max这三个属性的实现从Keep-Alive组件本身移到了渲染器层面,虽然开发时写起来没太大区别,但内部的缓存机制更严谨了,比如max淘汰策略从LRU(最近最少使用)变成了更符合前端场景的LFU(最近最不常使用)的优化版?不对,查过官方文档后明确是LRU,但队列管理更高效,不会再出现Vue2偶尔淘汰顺序不对的小问题,还有一点很重要,Vue3中Keep-Alive缓存的是组件实例的渲染上下文,包括ref、reactive、computed的状态,不再像Vue2那样只缓存DOM树的快照,所以组件内部的计时器、事件监听虽然不会自动销毁,但缓存的状态是完全独立的,不会因为组件销毁(其实是失活)而丢失。

新手用Keep-Alive最容易踩的5个雷区

第一个雷区就是刚才说的包裹方式不对,直接报错“Attribute is is required on <component> inside <keep-alive>”或者“<keep-alive> requires exactly one child component”,这个只要记住配合动态组件或者带v-slot的router-view就行。

第二个雷区是include/exclude没生效,首先得确认目标组件有没有写name属性,不管是Options API的name还是Setup语法糖的<script setup name="xxx">(Setup语法糖原来默认不暴露name,Vue3.2.34之后才支持直接在标签里加name,或者用defineOptions声明),name是include/exclude筛选的唯一标识,大小写要完全一致哦,比如组件里写的是<script setup name="UserList">,include写成include="userList"或者include="user_list"都不行,必须是include="UserList",如果用数组或者正则表达式的话,数组里的字符串、正则匹配的内容也要和组件name严格对应,还有一种情况是keep-alive没有包裹到真正的目标组件层级,比如嵌套路由里,只在根路由出口套了keep-alive,但子路由出口没处理,或者反过来,只在子路由出口套了,根路由没包裹导致子组件直接销毁,这时候得根据路由结构调整包裹位置。

第三个雷区是缓存的数据串了,很多人会把路由参数相关的状态直接存到组件的ref/reactive里,比如搜索关键词、分页页码,假设组件name是“UserList”,当你从用户列表的第1页跳到第2页再跳回来时,如果用了同一个UserList组件,就会直接复用缓存的第1页的状态,解决这个问题有两个办法,一个是给router-view加key,key的值用路由的fullPath,比如<component :is="Component" :key="route.fullPath" />,这样只要路由参数或者路径变了,组件就会强制销毁重建,不会复用缓存;另一个是用onActivated钩子,在组件激活时重新根据路由参数获取数据或者重置状态,同时可以用onDeactivated钩子清理临时状态或者暂停不必要的操作,这个办法更符合Keep-Alive的使用初衷,性能也更好。

第四个雷区是组件内部的计时器、事件监听没有清理,刚才说过Vue3中Keep-Alive失活时不会触发组件的onUnmounted钩子,只会触发onDeactivated,所以如果在onMounted里加了setInterval、addEventListener,在onUnmounted里清理的话,组件激活多次就会加多个计时器、多个事件监听,导致内存泄漏或者逻辑错误,正确的做法是把清理逻辑移到onDeactivated里,如果需要在组件真正销毁(比如max队列满了被淘汰、或者用户主动关闭标签页)时再做一次兜底清理,可以同时在onUnmounted里也加一遍。

第五个雷区是不知道怎么清理特定的缓存,比如用户从某个表单页离开后,再回来希望表单是空白的,或者用户登出后,希望所有缓存的个人信息相关页面都清空,这时候需要用到Keep-Alive的缓存实例集合,Vue3里Keep-Alive组件暴露了一个cache属性和keys属性,但直接操作这些属性是不被官方推荐的,而且Setup语法糖里也很难直接拿到父组件的Keep-Alive实例,那怎么办呢?有两个常用的办法,一个是用provide/inject,在包裹Keep-Alive的父组件里provide一个清理缓存的方法,子组件里inject调用;另一个是给keep-alive加一个key,当需要清理所有缓存时,直接修改这个key的值,这样Keep-Alive组件就会强制重建,内部的cache和keys也会清空,如果只需要清理特定的缓存,可以修改include/exclude数组,先把目标组件的name移除,等下一次渲染完成后再放回去,这样对应的缓存实例就会被销毁重建。

Vue3 Keep-Alive的隐藏新特性

除了刚才提到的LFU优化版LRU队列、Setup语法糖支持直接加name,Vue3.4版本之后还新增了一个cache-handler属性,可以自定义缓存的存储和淘汰逻辑,这个特性非常强大,比如可以把组件缓存存到localStorage或者sessionStorage里,实现页面刷新后状态还能保留的效果;也可以自定义淘汰策略,比如优先淘汰体积大的组件、或者优先级低的组件。

举个自定义cache-handler存到sessionStorage的小例子:首先得定义一个符合要求的cache-handler对象,这个对象需要有get、set、delete、has这四个方法,分别对应获取缓存、设置缓存、删除缓存、判断是否存在缓存,然后在keep-alive上绑定这个对象,不过要注意的是,localStorage/sessionStorage只能存储字符串,所以存缓存实例之前需要把它序列化,取的时候再反序列化,但组件实例里的函数、DOM元素、symbol这些类型是不能直接序列化的,所以这个特性更适合存储一些简单的状态,或者用JSON.stringify的replacer函数和JSON.parse的reviver函数做特殊处理,也可以用一些专门的序列化库,比如flatted或者circular-json。

还有一个隐藏的小特性是,当keep-alive的max属性变化时,会立即触发缓存的清理,比如原来max是5,现在改成3,就会立即淘汰最近最少使用的2个组件实例。

Vue3 Keep-Alive的3个高频场景实战

第一个高频场景是列表页缓存状态,详情页不缓存,比如电商网站的商品列表页,用户滚动了很久到了第10页,点进去看某个商品的详情,再返回列表页时,希望能直接回到第10页的位置,这时候就可以用Keep-Alive,具体实现步骤是:给商品列表页的Setup语法糖加name,比如<script setup name="ProductList">;在根路由出口用带v-slot的router-view包裹keep-alive;在include里加“ProductList”,或者用route.meta来动态控制include,比如在路由配置里给商品列表页加meta.keepAlive: true,然后在父组件里定义一个ref数组keepAliveList,根据路由的meta.keepAlive来动态添加或移除组件name;同时给商品列表页的router-view加key吗?不用加key,加了key就会强制销毁重建,缓存就没用了;最后在商品列表页的onActivated钩子里面恢复滚动位置,滚动位置可以在onDeactivated钩子里面存到sessionStorage或者组件的ref里,不过存到ref里更简单,因为失活时ref的状态不会丢失。

第二个高频场景是表单页缓存输入内容,提交后再清空缓存,比如用户填了一个很长的表单,中途不小心点了别的页面,再回来时希望输入的内容还在,提交成功后再跳转到别的页面,下次再进来表单是空白的,具体实现步骤是:给表单页加name和meta.keepAlive: true;在父组件里动态控制include;在表单页的onActivated钩子里面不需要做什么,因为输入内容会自动缓存;在提交成功的回调函数里,调用父组件provide的清理特定缓存的方法,清理当前表单页的缓存;如果提交失败,就不用清理。

第三个高频场景是标签页式的单页应用,比如后台管理系统的标签栏,每个标签页对应一个路由,打开新标签页时缓存旧标签页的状态,关闭标签页时清理对应的缓存,切换标签页时快速显示缓存的内容,具体实现步骤是:定义一个ref数组tabs,用来存储当前打开的标签页信息,包括路由的path、name、title、meta等;给根路由出口的keep-alive的include绑定tabs数组里的组件name;给router-view加key吗?不用加key;在路由守卫里监听路由变化,如果是新标签页,就把对应的组件name和其他信息push到tabs数组里;在关闭标签页的事件里,先从tabs数组里移除对应的组件name,这样keep-alive就会自动清理对应的缓存,然后再跳转路由;切换标签页时直接修改路由的path,keep-alive会自动激活对应的组件实例。

总结一下

Vue3的Keep-Alive虽然和Vue2有一些区别,但核心功能都是一样的,都是为了缓存组件的状态,提升单页应用的体验,只要记住配合动态组件或者带v-slot的router-view使用,给目标组件加对name属性,用onActivated和onDeactivated钩子处理状态和资源,按需控制include/exclude和max,就能用得非常顺手,Vue3.4新增的cache-handler属性也非常值得探索,可以实现很多自定义的缓存效果。

最后还要提醒大家,Keep-Alive不是万能的,不要什么组件都缓存,因为缓存组件实例会占用内存,尤其是那些体积很大、数据量很多的组件,缓存太多会导致页面卡顿,只有那些用户会频繁切换、状态复杂、加载时间长的组件才需要缓存,比如列表页、表单页、筛选页等,而那些内容简单、加载时间短、状态单一的组件,比如首页、登录页、404页等,就不需要缓存了。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门