当前位置: 首页 > Web前端 > vue.js

Vuekeep-alive用法及缓存机制详解

时间:2023-03-31 22:34:26 vue.js

前言在VUE项目中,有些组件或者页面不需要多次渲染,所以有些组件需要有条件地“持久化”到内存中,但是这里的持久化不是简单的数据持久化,而是整个组件(包括数据和视图)的持久化。恰好VUE提供了内置组件来实现这一点。,当包装动态组件时,缓存不活动的组件实例而不是销毁它们。与类似,是一个抽象组件:它本身不渲染DOM元素,也不出现在组件的父组件链中。当组件在中切换时,其激活和去激活的生命周期钩子函数将相应执行。基本使用有两个版本。在vue2.1.0之前,大部分是这样实现的:[JavaScript]plaintextview_copycode_?010203040506070809101112131415161718192021newRouter({routes:[{name:'a'`,`path:'/a'`,`component:A,meta:{keepAlive:true}},{name:'b'`,`path:'/b'`,`component:B}]})配置后这样路由的路由元信息,a路由的$route.meta.keepAlive为true,b路由为false。所以为true的会包裹在keep-alive中,为false的会在外层。这样路由a就达到了被缓存的效果。如果还有路由要缓存,只需要在路由元素中加上keepAlive:true即可。vue2.1.0之后keep-alive新增了两个属性:include(被包含的组件缓存生效)和exclude(被排除的组件不缓存,优先级大于include)。include和exclude属性允许有条件地缓存组件。两者都可以表示为逗号分隔的字符串、正则表达式或数组。使用正则表达式或数组时,一定要使用v-bind。简单使用:[JavaScript]plaintextview_copycode_?01020304050607080910111213141516171819202122配合router-view使用建议使用2.1.0之后的版本作为缓存策略,代码更简洁,重复渲染少很多。Advanced进阶使用我们了解了基本的使用,但是在日常项目中并没有想象的那么简单,所以我们需要为整个项目设计一个缓存策略。如何让所有组件动态切换自己的缓存属性是一个值得考虑的问题。业务场景1、列表页进入详情页,详情页有表头和行列表。2.从详情页面的行列表进入行详情页面查看,然后返回详情页面。详细信息页面不刷新。如果修改了行详情页,我进入详情页时需要刷新。这样的业务场景在移动端非常常见。当然解决方案有很多,比如每次返回详情页时给详情页传递一个logo,然后在详情页根据logo或者最新的接口进行判断获取店铺中的数据.数据,这种判断不仅麻烦,而且无法实现页级缓存,而且很耗内存资源。如果你使用好keep-alive,你可以很容易地达到上面的效果。总体设计思路1.在store中写三个方法:setKeepAlive、setNoKeepAlive、getKeepAlive,作用是设置需要缓存的组件,清除不需要缓存的组件,获取缓存的组件。2、获取所有组件的name属性,在store中存储在一个数组中。3、App.vue挂载时获取缓存的组件数组。4.在页面路由拦截函数中,动态设置页面是否缓存。5.监听App.vue中store的变化,给include对应的数组赋值。具体实现1.在store中注册三个方法[JavaScript]纯文本view_copycode_?0102030405060708091011121314151617181920212223242526conststate={keepAliveComponents:[],}constgetters={getKeepAlive(state){returnstate.keepAlivestonestonekeepstate.(state,component){//判断keepAliveComponents中是否有之前设置的组件名,避免重复!state.keepAliveComponents.includes(component)&&state.keepAliveComponents.push(component)},setNoKeepAlive(state,component){//删除不缓存的组件constindex=state.keepAliveComponents.indexOf(component)index!==-1&&state.keepAliveComponents.splice(index,1)},}constactions={}exportdefault{state,getters,mutations,actions,}2.在路由集合中,设置要缓存的各个组件。[JavaScript]plaintextview_copycode_?123456routes.forEach((item)=>{//在路由全局钩子beforeEach中,根据keepAlive属性,统一设置页面的可缓存性//作用是每隔一段时间进入这个组件time,缓存一下store.commit(`'setKeepAlive'`,item.name)})exportdefaultroutes??注意:不是写成store.commit('setKeepAlive',item.component.name),而是item.姓名。应该写成store.commit('setKeepAlive',item.component.name),因为include接受组件的名字,但是在按需加载的情况下,打包后名字会变,所以在开始设计的时候你的项目总是尽量确保路由名称与组件名称一致。3.获取App.vue挂载时缓存的组件数组,默认全部缓存。[JavaScript]纯文本view_copycode_?010203040506070809101112131415161718192021222324254.动态改变页面的缓存属性。比如我想从详情页跳转到行详情,我不能让行详情在跳转前缓存起来。如果缓存了行详细信息,我每次输入都是一样的。所以我要清除缓存。">现在我需要从行明细跳转到其他关联文档,那我肯定需要把行明细缓存起来,不然再回到行明细时就什么都没有了。">5.监听缓存变化。动态设置就好了,但是现在我还是需要动态绑定include数组,所以每次页面跳转的时候都需要监听。[JavaScript]纯文本view_copycode_?010203040506070809101112131415161718192021222324252627282930现在用法说完了,不过它的原理好像是不是很清楚,那我们来看看他的实现。原理分析keep-alive的核心思想是将组件缓存为vnode,然后使用include中的数组进行匹配。如果匹配,则直接使用。如果exclude发生变化,相应的vnode将被销毁。源码分析直接贴源码。大概理解写注释里面[JavaScript]纯文本查看_复制代码_?00100200300400500600700800901001101201301401501601701801902002102202302402502602702802903003103203303403503603703803904004104204304404504604704804905005105205??3054055056057058059060061062063064065066067068069070071072073074075076077078079080081082083084085086087088089090091092093094095096097098099100101102103104105106107108109110111112113114115116117118119120121122import{isRegExp,remove}from'shared/util'import{getFirstComponentChild}from'core/vdom/helpers/index'typeVNodeCache={[key:string]:?VNode};//获取组件名称functiongetComponentName(opts:?VNodeComponentOptions):?string{returnopts&&(opts.Ctor.options.name||opts.tag)}//检测名称是否匹配函数functionmatches(pattern:string|RegExp|Array,name:string):boolean{//arrayif(Array.isArray(pattern)){returnpattern.indexOf(name)>-1}elseif(`typeofpattern==='string'`){//stringreturnpattern.split(`','`).indexOf(name)>-1}elseif(isRegExp(pattern)){//正则返回pattern.test(name)}/*istanbulignorenext*/returnfalse}//修正缓存函数pruneCache(keepAliveInstance:any,filter:Function){const{cache,keys,_vnode}=keepAliveInstancefor(constkeyincache){//取出缓存中的vnodeconstcachedNode:?VNode=cache[key]if(cachedNode){constname:?string=getComponentName(cachedNode.componentOptions)/*如果name不满足过滤条件,不是当前渲染的vnode,则销毁该vnode对应的组件实例(Vue实例),并从缓存中移除*/if(name&&!filter(name)){pruneCacheEntry(cache,key,keys,_vnode)}}}}functionpruneCacheEntry(cache:VNodeCache,key:string,keys:Array,current?:VNode){constcached=cache[key]if(cached&&(!current||cached.tag!==current.tag)){/*销毁vnode对应的组件实例(Vue实例)*/cached.componentInstance.$destroy()}cache[key]=nullremove(keys,key)}constpatternTypes:Array=[String,RegExp,Array]导出默认值{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(){/*监听include和exclude,修改时修改缓存*/this`.$watch('include',val=>{`pruneCache(`this`,name=>matches(val,name))})this`.$watch('exclude',val=>{`pruneCache(`this`,name=>!matches(val,name))})},render(){/*getslot插槽中的第一个组件*/constslot=this`.$slots.`defaultconstvnode:VNode=getFirstComponentChild(slot)constcomponentOptions:?VNodeComponentOptions=vnode&&vnode.componentOptionsif(componentOptions){//检查模式/*获取名称组件的,首先获取组件的name字段,否则就是组件的标签*/constname:?string=getComponentName(componentOptions)const{include,exclude}=thisif(//不包括(include&&(!name||!matches(include,name)))||//排除ed(exclude&&name&&matches(exclude,name))){returnvnode}const{cache,keys}=thisconstkey:?string=vnode.key==null//相同的构造函数可能会注册为不同的本地组件//所以只有cid是不够的(#3269)?componentOptions.Ctor.cid+(componentOptions.tag?`::${componentOptions.tag}`:''`)`:vnode.key/*ifcached从缓存中直接获取组件实例到vnode,并缓存如果还没有被缓存*/if(cache[key]){vnode.componentInstance=cache[key].componentInstance//让当前keyfreshestremove(keys,key)keys.push(key)}else{cache[key]=vnodekeys.push(key)//修剪最旧的条目/*keepAlive标志*/vnode.data.keepAlive=true}returnvnode||(slot&&slot[0])}}简单总结1.createdhook会创建一个缓存对象作为缓存容器,保存vnode节点的destroyedhook会在组件执行时清除缓存缓存中的所有组件实例被毁。2、在render函数中主要做了这些事情:?首先通过getFirstComponentChild获取第一个子组件,并获取组件的名称(如果有组件名称,直接使用组件名称,否则使用标签)。?接下来,名称将通过include和exclude属性进行匹配。如果匹配不成功(说明不需要缓存),则不做任何操作,直接返回vnode。vnode是一个VNode类型的对象。?include和exclude属性支持字符串,例如“a,b,c”,其中组件名称由逗号和正则表达式分隔。matches通过这两个方法检测是否匹配当前组件。?然后根据key在this.cache中查找。如果存在,说明之前缓存过。直接覆盖当前vnode上缓存的vnode的componentInstance(组件实例),否则将vnode存入缓存。最后返回vnode(有缓存时vnode的componentInstance已经替换为缓存中的)。3.如果需要监听变化,使用watch监听pruneCache和pruneCache这两个属性的变化,变化时修改cache缓存中的缓存数据。4、Vue.js内部将DOM节点抽象为一个个的VNode节点,保活组件的缓存也是基于VNode节点,而不是直接存储DOM结构。它会将满足条件的组件(pruneCache和pruneCache)缓存到缓存对象中,然后在需要重新渲染的时候从缓存对象中取出vnode节点进行渲染。文章转载自:https://juejin.im/post/5eb143...