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

vue-router记录路由历史,完美解决fallbackcache

时间:2023-03-31 22:14:55 vue.js

单页面框架中一个通病就是地址fallback的pagecache,即从某一个页面返回到上一个页面不重新加载,保留上次离开时??间状态。简介是vue的内置组件,是一个抽象组件,接受3个属性值:include-字符串或正则表达式。只有具有匹配名称的组件才会被缓存。exclude-字符串或正则表达式。名称匹配的任何组件都不会被缓存。最大值-一个数字。可以缓存的最大组件实例数。当超过上限时,缓存组中最长时间未被访问的组件将被销毁。当使用包装组件时,不活动的组件实例被缓存而不是销毁,这排除了功能组件,因为它们没有实例。vue-router中缓存页面的一般写法如下缓存的组件在激活和去激活时会触发activated和deactivated钩子。history和location接口前端更新浏览器地址主要有location和history两个接口,除了会重新加载页面的api:location.hash=[string]-不会重新加载页面和触发hashchange事件history。pushState(...),history.replaceState(...)-页面更新和替换,不触发任何事件history.back(),history.forward()-前进和后退,触发hashchange/popstate事件,按钮浏览器本身的功能类似于这个history.go([number])-当参数为0时,相当于reload,重新加载页面;不为0时,类似于上面的back和forward。另一方面,vue-router提供了hash和state两种模式,默认使用state。支持html5的环境会降级为hash。它们和api的关系以及会触发的事件可以在下表中查看api或运行vue-router模式触发的事件location.hash=...hash*hashchangehistory.pushState(...),history.replaceState(...)statenonehistory.back(),history.forward(),history.go(...)allhashchange/popstate点击浏览器前进/后退allhashchange/popstate*注:如果vue-router是设置为hash模式,不一定调用location.hash方法,查看源码,可以看到底层还是先调用pushState方法,不支持的环境会降级为location.hash。//vue-router源码functionpushHash(path){if(supportsPushState){pushState(getUrl(path));}else{window.location.hash=path;}}functionreplaceHash(path){if(supportsPushState){replaceState(getUrl(path));}else{window.location.replace(getUrl(path));}}vue-route缓存历史的难点直观上,只需要页面组件的名称,实现起来不会很容易灾难。其实include的值在正向路由和反向路由的时候是一定要变的,不然会出现很多混乱。考虑这种情况:routeA和routeB都需要缓存。从routeA进入routeB,再回到routeA,此时routeB处于cacheinactive状态。如果此时进入routeB,看到的是缓存的页面,而不是Refresh,这明显会bug。正确的做法是从routeB返回后,把routeB从include中去掉。因此,非常有必要随着路由的前后移动修改include,保证只缓存历史中的路由。一般的做法是使用全局钩子,但是钩子无法判断是前进还是后退。这是我的方法。Vuex存储路由数据,先不考虑代码如何实现,先设计存储历史路由数据的数据结构。很显然,数组是最直观的,路由变化操作对应的是增加或删除数组的最后一项。此时的数据结构如下:但是有一种特殊情况。浏览器后退再前进时,此时只会触发两次pop,pop事件没有url地址,所以无法获取到必要的信息。看起来浏览器的路由历史没有任何变化,只是数组的最后一项是空的。因此,删除数组的最后一项在返回时不起作用。一种方式是保存所有的路由,然后使用索引来标识当前路由的位置。同时设置一个参数direction来标识路由是前向还是后向。改模型后的数据结构如下:用vuex保存数据。//存储数据结构state={records:[],//历史路由数组index:0,//当前路由索引direction:'',//历史变化方向,向前/向后}路由变化时对应数据变化推送Newroute,向数组添加新数据,direction为forwardreplaceroute,数组最后一项替换数据,direction为forwardpopback/forward,改变index索引,direction需要判断路由记录,作为模块单独写://history.js//assumeroutemeta包含keepAlive和componentName属性constformRecord=(vueRoute)=>{return{name:vueRoute.name,path:vueRoute.fullPath,keepAlive:vueRoute.meta&&vueRoute.meta.keepAlive,组件名:r.meta&&r.meta.componentName?r.meta.componentName:''}}exportdefault{namespaced:true,state:{records:[],//历史路由数组index:0,//当前路由索引direction:'',//历史变化方向,前向/后向},getters:{routes:state=>{const{records,index}=stateif(records.length>0&&index0为前进,<0为后退,path为当前位置.hrefPOP_ROUTE(state,{count,路径}){let{records,index,direction}=stateif(count){direction=count>0?'forward':'backward'工业ex+=countindex=Math.min(records.length,index)index=Math.max(0,index)}else{if(index>0&&records[index-1].path===path){//backwarddirection='backward'index-=1}elseif(index{/*...*/})但是试了下,发现beforeEach只给出了路由信息,却没有给出导致路由变化的方法。不管是replace还是push,不知道方法的话,路由数组都是不准确的。全局钩子的想法只能放弃。从以上两点来看,无法准确监控路由的推送和替换。这也需要我们转变思路,不是去监测路线变化,而是想办法去想办法引起变化。可以看到vue-router的输出是一个对象,里面包含了push和replace方法。我们可以继承对象重写方法,在调用时记录路由(当然我们也可以自定义事件)。继承VueRouter对象类myRouterextendsVueRouter{push(){...super.push()}replace(){...super.replace()}}这样push和replace就记录下来了,如何处理弹出事件。pop其实分为两种情况,一种是router.go(),另一种是用户操作浏览器前进/后退时。前者可以重写router,后者需要使用hook事件,判断不是router方法导致的。完整的代码如下,onAbort)}replace(location,onComplete,onAbort){routerTrigger=truestore.commit('history/REPLACE_ROUTE',super.resolve(location).resolved)super.replace(location,onComplete,onAbort)}go(n)复制代码{if(n!==0){routerTrigger=truestore.commit('history/POP_ROUTE',{count:n})super.go(n)}else{window.location.reload()}}}const路由器=newMyRouter(...)router.afterEach((to,from)=>{if(to.matched.length>0&&store.state.history.records.length===0){store.commit('history/PUSH_ROUTE',to)}elseif(!routerTrigger&&to.fullPath){store.commit('history/POP_ROUTE',{path:to.fullPath})}routerTrigger=false})ap在p.vue中,可以计算需要缓存的组件数组computed:{...mapGetters('history',['routes']),keepAliveComponents(){letarray=[]if(this.routes){array=this.routes.filter(r=>!!r.keepAlive).map(h=>h.componentName)}returnarray}}因为vuex中记录了所有的历史路由,所以可以更细粒度的控制缓存数组。比如在store中加入一个人工数组,每次获取history数组时调整路由的keep-alive值//store...state:{manualRecords:[],},getters:{routes:state=>{constroutes=[]const{records,index}=stateif(records.length>0&&index{constm=this.manualRecords.find((i)=>i.name===item.name)if(m)item.keepAlive=m.keepAlivereturnitem})returnroutes}},mutations:{EDIT_KEEPALIVE(state,routeName,keepAlive){let{manualRecords}=statemanualRecords=manualRecords.filter((item)=>item.name!==routeName)manualRecords.push({name:routeName,keepAlive})state.manualRecords=manualRecords}}