vue单页应用前进刷新后退使用缓存的实现

目录

前言

问题场景

一、页面A->页面B->页面C

二、页面A->页面B->页面C->页面B

解决方案

(1) keep-alive时限前进刷新后退使用缓存

(2)结合vuex实现前进刷新后退使用缓存

注:


前言

vue-cli创建的创建结合keep-alive可以实现页面缓存的效果。但是,在实际的使用过程中,发现后退返回使用缓存,前进进入也是使用的缓存,页面不刷新。这对实际的应用来说不是太方便。在网上断断续续查了有不少人说的解决方案,但是都没有一种很终极很理想的解决方案。这篇博文结合我实际项目中的应用场景,分享下vue项目单页应用的关于缓存的使用。

问题场景

一、页面A->页面B->页面C

操作要求:

只有下一页前进操作,和返回上一页操作。不存在提交跳转的使用场景。那么要求无论页面是否设置了缓存,都要求前进时重新刷新,后退时使用缓存。项目中可能的应用场景如下:

A也是入口首页,B是列表,C是详情。C返回B还是原来的列表,但是B页面提供的有下拉刷新的功能。

二、页面A->页面B->页面C->页面B

操作要求:

A页面是入口页面,B页面是列表,C页面是详情。

在详情C页面,可能直接点击返回按钮返回到页面B,也可能点击提交按钮进行提交操作,还是返回到页面B,但是要求页面B进行刷新。也就是说,具备可能缓存要求的页面再次进入的时候有三种情况,即:前进进入一定重新刷新,“返回”(当前页面的上个页面就是点击按钮要进入的页面)进入的页面分两种情况,一种是刷新,一种是使用缓存。

解决方案

上面两种不同的应用场景,有不同的解决方案,使用的技术也不尽相同。但是都要结合keep-alive。其中第二种应用场景必须结合vuex,但是第一种简单场景则单单使用keep-alive就能实现。

(1) keep-alive时限前进刷新后退使用缓存

单单使用keep-alive实现场景一的方案需要借助meta.keepAlive属性来完成。代码如下:

在root.vue对router-vier进行如下调整:

<keep-alive>
    <router-view v-if="$route.meta.keepAlive">
</router-view>
</keep-alive>
    <router-view v-if="!$route.meta.keepAlive">
</router-view>

简单说,就是通过给组件设置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的特性决定的。

(2)结合vuex实现前进刷新后退使用缓存

根据场景二的情况,使用keep-alive的include和exclued属性结合vuex动态完成改变那些组件需要刷新,那些组件使用缓存。

首先,修改root.vue组件,代码如下:
 

<keep-alive :include="includedComponents" :exclude="excludedComponents">
        <router-view></router-view>
</keep-alive>

在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中,就正常了。

这就说明,路由钩子函数的执行和组件是否缓存没有关系,他都会按照正常的流程进行执行。

猜你喜欢

转载自blog.csdn.net/liangcha007/article/details/84763438