目录
前言
问题场景
一、页面A->页面B->页面C
二、页面A->页面B->页面C->页面B
解决方案
(1) keep-alive时限前进刷新后退使用缓存
(2)结合vuex实现前进刷新后退使用缓存
注:
vue-cli创建的创建结合keep-alive可以实现页面缓存的效果。但是,在实际的使用过程中,发现后退返回使用缓存,前进进入也是使用的缓存,页面不刷新。这对实际的应用来说不是太方便。在网上断断续续查了有不少人说的解决方案,但是都没有一种很终极很理想的解决方案。这篇博文结合我实际项目中的应用场景,分享下vue项目单页应用的关于缓存的使用。
操作要求:
只有下一页前进操作,和返回上一页操作。不存在提交跳转的使用场景。那么要求无论页面是否设置了缓存,都要求前进时重新刷新,后退时使用缓存。项目中可能的应用场景如下:
A也是入口首页,B是列表,C是详情。C返回B还是原来的列表,但是B页面提供的有下拉刷新的功能。
操作要求:
A页面是入口页面,B页面是列表,C页面是详情。
在详情C页面,可能直接点击返回按钮返回到页面B,也可能点击提交按钮进行提交操作,还是返回到页面B,但是要求页面B进行刷新。也就是说,具备可能缓存要求的页面再次进入的时候有三种情况,即:前进进入一定重新刷新,“返回”(当前页面的上个页面就是点击按钮要进入的页面)进入的页面分两种情况,一种是刷新,一种是使用缓存。
上面两种不同的应用场景,有不同的解决方案,使用的技术也不尽相同。但是都要结合keep-alive。其中第二种应用场景必须结合vuex,但是第一种简单场景则单单使用keep-alive就能实现。
单单使用keep-alive实现场景一的方案需要借助meta.keepAlive属性来完成。代码如下:
在root.vue对router-vier进行如下调整:
简单说,就是通过给组件设置meta.keepAlive属性来显示缓存组件,还是重新刷新显示组件。我看到有些网页写到这一步,然后在router的index.js中给路由配置meta的默认值来完成页面缓存。这种方式只能在任何情况下第二次进入需要缓存的页面都不会刷新,即从A->B除了第一次会刷新外,后面都是走的缓存,这名下不符合需求。
后来又看到有网友说声明一个全局的beforeEach,维护一个路由列表,通过判断to.name是否是路由列表中最后一个路由判断是后退还是前进,进而设置页面是否缓存。我尝试了他的代码和方法,发现有些场景还是不能满足,例如第一次返回的时候,B页面还是会刷新,因为从A首次进入B的时候,B的meta.keepAlive=false,只有在从C返回的时候才设置成了true,此时从B进入C再返回B,B是缓存页面。但是再返回A从新走,又回走前面的流程,也就是说对于B页面始终又一次多余的刷新。但是他这种思路是可行的,我再他的基础上,进行了优化,除了维护一个路由列表外,我还要维护一个需要缓存的组件列表。代码如下:
var routerList = [];
var keepAlived = ['dispatchIndex', 'serviceIndex', 'manageIndex'];
router.beforeEach((to, from, next) => {
var li = routerList.length;
if (li > 0 && routerList[li - 1] == to.name) { // 后退
routerList.splice(routerList.length - 1, 1)
if (keepAlived.indexOf(from.name) > -1) {
from.meta.keepAlive = true;
}
} else { // 前进
if (!ctool.strIsEmpty(from.name)) {
routerList.push(from.name);
if (keepAlived.indexOf(to.name) > -1) {
if (to.meta.keepAlive) {
to.meta.keepAlive = false;
} else {
to.meta.keepAlive = true;
}
}
if (keepAlived.indexOf(from.name) > -1) {
from.meta.keepAlive = true;
}
} else {
console.log("-------------");
}
}
next()
})
整体思路还是一样,但是对代码进行了调整和优化,确保不会出现多余刷新的情况。
注:关于这种方式,需要说明的是,缓存的页面除非你在页面内部手动刷新,否则缓存不会自己刷新。也就说你给组件的meta.keepAlived设置true或者false,只是不过是修改其在那个router-view里渲染展示而已,而对于已经缓存过的组件,如果又让其显示缓存,那么还是显示的原来的缓存页面。
这种情况就造成场景二里提交返回时候列表的问题,列表即便刷新了,但是如果你又想使用缓存的话,还是原来第一次进入的缓存页面。这是这种解决方案自身决定的,也是keep-alive的特性决定的。
根据场景二的情况,使用keep-alive的include和exclued属性结合vuex动态完成改变那些组件需要刷新,那些组件使用缓存。
首先,修改root.vue组件,代码如下:
在root.vue的计算属性中,代码如下:
computed:{
includedComponents(){
return this.$store.state.includedComponents;
},
excludedComponents(){
return this.$store.state.excludedComponents;
}
}
可以看到,哪些组件需要缓存,那些不需要缓存是通过vuex来动态存储的。
完成以上代码之后,同样的还是需要在main.js中进行全局路由守卫的编写,代码如下:
var routerList = [];
router.beforeEach((to,from,next)=>{
var li = routerList.length;
console.log(store.state.includedComponents);
if(li > 0 && routerList[li - 1] == to.name){
/*
如果发现to.name等于list中当前最后一个,则说明是返回操作。
返回操作的时候,第一步是从list中清掉第一个路由对象。
第二步是判断一下当前的from.name是不是在缓存属性中,在的话,就从里面拿掉,因为下一次进入的时候,
要重新刷新。
*/
routerList.splice(routerList.length - 1, 1);
if(store.state.includedComponents.indexOf(from.name)>-1){
console.log('rm',from.name);
store.commit('removeInclude',from.name);
store.commit('addToExclude',from.name);
}
}else{
if (!ctool.strIsEmpty(from.name)) {
routerList.push(from.name);
if (store.state.excludedComponents.indexOf(to.name) > -1) {
console.log('ad',to.name);
store.commit('removeExclude', to.name);
store.commit('addToInclude', to.name);
}
}
}
next();
});
这里可以看到,操作的逻辑其实也还是借助维护的路由列表来判断是前进还是后退。但是单单是这样,还不能解决问题,因为我们还要根据实际的业务场景来动态改变那些页面需要缓存,那些不需要缓存。在vuex的store.js中代码如下:
import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);
const state = {
includedComponents:['dispatchIndex', 'serviceIndex', 'manageIndex'],
excludedComponents:[]
}
const mutations = {
removeInclude(state,str){
state.includedComponents.splice(state.includedComponents.indexOf(str),1);
},
addToInclude(state,str){
state.includedComponents.push(str);
},
removeExclude(state,str){
state.excludedComponents.splice(state.excludedComponents.indexOf(str),1);
},
addToExclude(state,str){
state.excludedComponents.push(str);
}
}
var store = new vuex.Store({
state:state,
mutations:mutations
})
export default store;
很多时候并不是说所有页面都需要缓存的,所以需要缓存的页面还是需要提前设置在includedComponents数组中。然后我在utils.js中进行公共方法的封装,代码如下所示:
clearCache:function(router_name){
store.commit('removeInclude',router_name);
store.commit('addToExclude',router_name);
}
这个方法用来将页面缓存清除。
至此所有需要的代码都已完成。
按照场景二中的情况,在页面C中,如果我是正常的返回,那么直接使用this.$router.go(-1);
如果我在C页面中,点击提交按钮回到B页面,但是需要B页面需要刷新,则点击提交按钮的代码如下写:
ctool.clearCache('B');//ctool挂载在windows上全局对象
this.$router.go(-1);
这样返回到B页面,B页面就会重新刷新。
在实际的开发中,还发现一个很好玩的现象。有一个组件C,设置其进行缓存,但是发现缓存失败,每次正常返回的时候还是会重新刷新页面,通过查看到发现,组件C的获取数据并刷新的操作是在组件的beforRouterEnter钩子函数中通过vm=>fun方式来调用的。后来写到create中,就正常了。
这就说明,路由钩子函数的执行和组件是否缓存没有关系,他都会按照正常的流程进行执行。