1. 程式人生 > >vue單頁應用前進重新整理後退使用快取的實現

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

這就說明,路由鉤子函式的執行和元件是否快取沒有關係,他都會按照正常的流程進行執行。