1.vue缓存的源码keepalive页面刷新数据的方法
2.keep-alive的vue2和vue3的源码以及LRU算法
3.Vue3源码系列 (六) KeepAlive
4.Vue3-KeepAliveè·¯ç±ç¼å详解
5.利用Vue中keep-alive,快速实现页面缓存
vue缓存的源码keepalive页面刷新数据的方法
用到这个的业务场景是这样的:
a页面点击新建列表按钮进入到新建的页面b,填写b页面并点击b页面确认添加按钮,源码把这些数据带到a页面,源码填充到列表(数组),源码可以添加多条。源码通告程序源码
点击这条的源码时候进入到编辑页面,确认修改之后,源码回退到a页面,源码a页面需要更新这条数据
实现这个功能的源码时候,由于是源码路由页面之间的跳转,首先想到的源码方案有几个:1. 用sessionStorage本地存储;2. 用路由参数带过去;3. 用兄弟组件传值
由于是添加完之后如果按回退是需要退出整个页面,如果用路由跳转,源码会出现回退到编辑页面了,源码所以这个方法首先排除
用本地存储的源码时候,如果不加回退也会吧之前存的历史记录给带过来,所以最终选用了第三种方法
由于a页面需要缓存,所以用到了路由页面的缓存
router.js
{ path: '/a',name: 'a',component: a,meta: { keepAlive: true}},
App.vue
<template> <div id="app"> <keep-alive> <router-view v-if="$routemetakeepAlive"></router-view> </keep-alive> <router-view v-if="!$routemetakeepAlive"></router-view> </div></template>
由于用到了keepalive, 所以页面在再次加载的时候是不会触发created而是会触发activated的
所以接收数据,重置数据要写到activated方法下
a页面接收
更改用Vue.set()方法
调用方法:Vue.set( target,自助下单商城iapp源码 key, value )
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据
value :重新赋的值
activated(){ let that = this Self$on('detailShow',(data)=>{ // 接收 if(!dataisEdit){ // 是新增还是编辑--这个是在跳转的时候带过去的--新增/编辑页面也会根据这个显示内容有所区别 thataddParamspush(data) thataddParams = Arrayfrom(new Set(thataddParamsSubsidyInfos)) // 为避免重复去个重 }else{ // thataddParams[thateditIndex] = data // 刚开始想通过直接修改,后来发现不行,因为页面是有缓存的,显示的还是未修改之前的 Vueset(thataddParams,thateditIndex,data) // 用set方法修改数据 } }) },
b页面提交(新增/编辑)
submit(){ if(!thisdetailValidate()){ // 这个是表单校验,如果不通过不然提交 return; }else{ Self$emit('detailShow',thisaddParams) // 事件分发 this$routerback(); } }
vue 缓存的keepalive页面刷新数据的话,这个主要还是用到了Vue.set( target, key, value )方法
keep-alive的vue2和vue3的源码以及LRU算法
0.LRU算法
LRU(leastrecentlyused)根据数据的历史记录来淘汰数据,重点在于保护最近被访问/使用过的数据,淘汰现阶段最久未被访问的数据
LRU的主体思想在于:如果数据最近被访问过,那么将来被访问的几率也更高
经典的LRU实现一般采用双向链表+Hash表。借助Hash表来通过key快速映射到对应的链表节点,然后进行插入和删除操作。这样既解决了hash表无固定顺序的缺点,又解决了链表查找慢的缺点。
但实际上在js中无需这样实现,可以参考文章第三部分。先看vue的keep-alive实现。
1.keep-alivekeep-alive是vue中的内置组件,使用KeepAlive后,被包裹的组件在经过第一次渲染后的vnode会被缓存起来,然后再下一次再次渲染该组件的乱斗时空游戏源码时候,直接从缓存中拿到对应的vnode进行渲染,并不需要再走一次组件初始化,render和patch等一系列流程,减少了script的执行时间,性能更好。
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive
从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive属性判断是否需要缓存。
2.vue2的实现实现原理:通过keep-alive组件插槽,获取第一个子节点。根据include、exclude判断是否需要缓存,通过组件的key,判断是否命中缓存。利用LRU算法,更新缓存以及对应的keys数组。根据max控制缓存的最大组件数量。
先看vue2的趣天麦网源码实现:
exportdefault{ name:'keep-alive',abstract:true,props:{ include:patternTypes,exclude:patternTypes,max:[String,Number]},created(){ this.cache=Object.create(null)this.keys=[]},destroyed(){ for(constkeyinthis.cache){ pruneCacheEntry(this.cache,key,this.keys)}},mounted(){ this.$watch('include',val=>{ pruneCache(this,name=>matches(val,name))})this.$watch('exclude',val=>{ pruneCache(this,name=>!matches(val,name))})},render(){ constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)constcomponentOptions:?VNodeComponentOptions=vnode&&vnode.componentOptionsif(componentOptions){ //checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}vnode.data.keepAlive=true}returnvnode||(slot&&slot[0])}}可以看到<keep-alive>组件的实现也是一个对象,注意它有一个属性abstract为true,是一个抽象组件,它在组件实例建立父子关系的时候会被忽略,发生在initLifecycle的过程中:
//忽略抽象组件letparent=options.parentif(parent&&!options.abstract){ while(parent.$options.abstract&&parent.$parent){ parent=parent.$parent}parent.$children.push(vm)}vm.$parent=parent然后在?created?钩子里定义了?this.cache?和?this.keys,用来缓存已经创建过的?vnode。
<keep-alive>直接实现了render函数,执行<keep-alive>组件渲染的时候,就会执行到这个render函数,接下来我们分析一下它的实现。
首先通过插槽获取第一个子元素的vnode:
constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)<keep-alive>只处理第一个子元素,所以一般和它搭配使用的有component动态组件或者是router-view。
然后又判断了当前组件的名称和include、exclude(白名单、黑名单)的关系:
//checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}functionmatches(pattern:string|RegExp|Array<string>,name:string):boolean{ if(Array.isArray(pattern)){ returnpattern.indexOf(name)>-1}elseif(typeofpattern==='string'){ returnpattern.split(',').indexOf(name)>-1}elseif(isRegExp(pattern)){ returnpattern.test(name)}returnfalse}组件名如果不满足条件,那么就直接返回这个组件的vnode,否则的话走下一步缓存:
const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}如果命中缓存,则直接从缓存中拿vnode的组件实例,并且重新调整了key的linux 头文件源码模板顺序放在了最后一个;否则把vnode设置进缓存,如果配置了max并且缓存的长度超过了this.max,还要从缓存中删除第一个。
这里的实现有一个问题:判断是否超过最大容量应该放在put操作前。为什么呢?我们设置一个缓存队列,都已经满了你还塞进来?最好先删一个才能塞进来新的。
继续看删除缓存的实现:
functionpruneCacheEntry(cache:VNodeCache,key:string,keys:Array<string>,current?:VNode){ constcached=cache[key]if(cached&&(!current||cached.tag!==current.tag)){ cached.componentInstance.$destroy()}cache[key]=nullremove(keys,key)}除了从缓存中删除外,还要判断如果要删除的缓存的组件tag不是当前渲染组件tag,则执行删除缓存的组件实例的$destroy方法。
————————————
可以发现,vue实现LRU算法是通过Array+Object,数组用来记录缓存顺序,Object用来模仿Map的功能进行vnode的缓存(created钩子里定义的this.cache和this.keys)
2.vue3的实现vue3实现思路基本和vue2类似,这里不再赘述。主要看LRU算法的实现。
vue3通过set+map实现LRU算法:
constcache:Cache=newMap()constkeys:Keys=newSet()并且在判断是否超过缓存容量时的实现比较巧妙:
if(max&&keys.size>parseInt(maxasstring,)){ pruneCacheEntry(keys.values().next().value)}这里巧妙的利用Set是可迭代对象的特点,通过keys.value()获得包含keys中所有key的可迭代对象,并通过next().value获得第一个元素,然后进行删除。
3.借助vue3的思路实现LRU算法Leetcode题目——LRU缓存
varLRUCache=function(capacity){ this.map=newMap();this.capacity=capacity;};LRUCache.prototype.get=function(key){ if(this.map.has(key)){ letvalue=this.map.get(key);//删除后,再set,相当于更新到map最后一位this.map.delete(key);this.map.set(key,value);returnvalue;}return-1;};LRUCache.prototype.put=function(key,value){ //如果已经存在,那就要更新,即先删了再进行后面的setif(this.map.has(key)){ this.map.delete(key);}else{ //如果map中不存在,要先判断是否超过最大容量if(this.map.size===this.capacity){ this.map.delete(this.map.keys().next().value);}}this.map.set(key,value);};这里我们直接通过Map来就可以直接实现了。
而keep-alive的实现因为缓存的内容是vnode,直接操作Map中缓存的位置代价较大,而采用Set/Array来记录缓存的key来模拟缓存顺序。
参考:
LRU缓存-keep-alive实现原理
带你手撸LRU算法
Vue.js技术揭秘
原文;/post/Vue3源码系列 (六) KeepAlive
KeepAlive组件用于缓存组件状态,它本身不渲染DOM元素或出现在父组件链中。适配单一组件使用,与component或router-view协同工作。
KeepAlive的核心实现为KeepAliveImpl。其包含组件名称、判断是否为KeepAlive的标记、属性类型和setup方法。KeepAlive与实例化渲染器通过上下文传递信息。在当前实例的上下文对象ctx中,暴露激活与去激活方法activate和deactivate。
在setup中,获取当前实例上下文并挂载activate和deactivate。激活时,通过patch方法对比更新,同步props变更,组件设为非去激活状态,调用实例的onActived钩子。去激活操作类似。组件卸载及销毁缓存方法在setup返回函数内实现。
使用watch API监控include、exclude变化,依据match函数筛选出缓存组件,用于销毁操作。onMounted、onUpdated、onBeforeUnmount安排缓存子组件树及组件onDeactived钩子调用,最后组件卸载。setup返回的函数确保只对插入的单个组件有效。
当rawVNode为默认插槽的第一个元素,直接返回组件,跳过缓存流程。异步组件返回rawVNode,缓存执行。若rawVNode未直接返回且非异步组件,则依据逻辑返回组件或执行缓存程序。
KeepAlive组件实质即KeepAliveImpl,重申类型。onActived和onDeactived生命周期钩子通过registerKeepAliveHook注册。此函数包装钩子并注入KeepAlive,确保遍历组件树时仅查找KeepAlive中的钩子列表,组件卸载时移除相应钩子。
Vue3-KeepAliveè·¯ç±ç¼å详解
ç¨æ³
<keep-alive>å 裹å¨æç»ä»¶æ¶ï¼ä¼ç¼åä¸æ´»å¨çç»ä»¶å®ä¾ï¼èä¸æ¯éæ¯å®ä»¬ã<keep-alive>æ¯ä¸ä¸ªæ½è±¡ç»ä»¶ï¼å®èªèº«ä¸ä¼æ¸²æä¸ä¸ªDOMå ç´ ï¼ä¹ä¸ä¼åºç°å¨ç»ä»¶çç¶ç»ä»¶é¾ä¸ãå½ç»ä»¶å¨<keep-alive>å 被åæ¢æ¶ï¼å®çmountedåunmountedçå½å¨æé©åä¸ä¼è¢«è°ç¨ï¼åè代ä¹çæ¯activatedådeactivated.
使ç¨å¨è·¯ç±ç»ä»¶é ç½®æ件å½ä¸é ç½®ç»ä»¶æ¯å¦éè¦è¢«ç¼åï¼é ç½®metaå±æ§{ path:'/dynamic',name:'Dynamic',component:()=>import('@/views/Dynamic.vue'),meta:{ keepAlive:true,showFooter:true,}},å¨æ ¹ç»ä»¶App.vueä¸ä½¿ç¨keep-aliveæ ç¾å å«éè¦ç¼åçç»ä»¶<!--è¿æ¯vue3çåæ³ï¼ä¸vue2çåæ³ææåºå«ï¼å¦æè¿é使ç¨vue2.xåæ³ï¼ä¼æè¦å--><router-viewv-slot="{ Component}"><keep-alive><component:is="Component":key="$route.name"v-if="$route.meta.keepAlive"/></keep-alive><component:is="Component":key="$route.name"v-if="!$route.meta.keepAlive"/></router-view>ç¼å页é¢ä½¿ç¨onActivated(()=>{ //被å 裹ç»ä»¶è¢«æ¿æ´»çç¶æä¸è§¦å//é»è¾ä»£ç }onDeactivated(()=>{ //å¨è¢«å 裹ç»ä»¶åæ¢ä½¿ç¨æ¶è§¦å//é»è¾ä»£ç })注æ页é¢ç¬¬ä¸æ¬¡è¿å ¥çæ¶åï¼é©å触åç顺åºæ¯created->mounted->activatedï¼æ以å«å¨mountedåactivatedæ¹æ³ä¸åç¸åçé»è¾ä»£ç
å¤é¡µé¢ç¼åéè¦é ç½®keyå±æ§ï¼æ¥è¡¨ç¤ºç»ä»¶çå¯ä¸æ§å对åºå ³ç³»ï¼å¦ï¼:key="$route.path"
ä¸è¦å¨æä¿®æ¹meta.keepAliveçå¼æ§å¶æ¯å¦ç¼åï¼å 为第ä¸æ¬¡æ¯å建ç»ä»¶ï¼æ²¡æç¼åï¼éè¦ç¼ååï¼ä¸ä¸æ¬¡è¿å ¥æä¸ä¼è§¦åé»è¾ä»£ç ï¼å¦ææå¼å§è¿å ¥çæ¶åmeta.keepAliveå¼ä¸ºfalseçè¯ï¼æ¸²æçæ¯æ²¡æ使ç¨keep-aliveçç»ä»¶ï¼åä¼è§¦åç¸åºä»£ç
利用Vue中keep-alive,快速实现页面缓存
使用Vue的keep-alive组件,可以实现页面的缓存,避免组件重复渲染,提升用户体验和性能。该组件用于保存组件状态或避免重新渲染。在使用时,需注意以下几点:保持组件实例不销毁,利用生命周期钩子函数activated和deactivated来管理组件状态。对于多级菜单或列表页+详情页的场景,使用keep-alive组件能显著改善回退时的体验。
在实际项目中,整合keep-alive的步骤如下:
1.修改App.vue文件,确保全局配置。
2.在路由配置中,为需要缓存的页面设置keepAlive属性。
3.调整beforeEach钩子,实现页面切换时的缓存清理逻辑。
为了维护页面的滚动位置,可引入以下策略:
由于keep-alive组件不自动记录滚动位置,需在页面跳转时保存当前滚动位置。在页面激活时,恢复滚动位置。具体实现包括记录滚动位置、在activated钩子中应用滚动恢复。
鼓励读者在下方留言分享实践经验和疑问,更多学习资源和成长机会,欢迎关注微信公众号“一郭鲜”。