Vue專案全域性配置頁面快取,實現按需讀取快取

FrankCheung發表於2018-07-30

這篇文章也發在我的部落格,歡迎圍觀?

寫在前面

一個web app的實際使用場景中,有一些情景的互動要求,是記錄使用者的瀏覽狀態的。最常見的就是在列表頁進入詳情頁之後,再返回到列表頁,使用者希望返回到進入詳情頁之前的狀態繼續操作。但是有些使用場景,使用者又是希望能夠獲取最新的資料,例如同級列表頁之間切換的時候。

如此,針對上述兩種使用場景,需要實現按需讀取頁面快取。由於SPA應用的路由邏輯也是在前端實現的,因此可以在前端對路由的邏輯進行設定以實現所需效果。

使用技術

  1. Vue.js作為主要框架
  2. Vue-router作為前端路由管理器
  3. Vuex作為狀態管理工具

總體思路

keep-alive判斷當前元件是否讀取快取的節點,在整個生命週期裡面非常靠後,在afterEach之後,基本在元件例項建立之前。(因此在此之前對當前元件是否讀取快取進行處理都是可行的,我選擇在全域性前置守衛進行處理)

而判斷當前元件是否快取的節點,則早於元件的beforeRouteLeave鉤子。

基於上述邏輯,本方案解決的邏輯是,對當前開啟的頁面進行判斷,動態生成需要keepAlive的元件陣列配置,對有可能需要快取的先行進行快取,然後在每次路由切換的時候,再進行判斷,按需讀取頁面快取。

  1. 使用kepp-alive進行快取,使用include屬性對需要快取的頁面進行配置。
  2. 由於需要快取的頁面配置系動態生成,所以使用vuex儲存該配置。
  3. 在路由元資訊中寫入兩個配置,一是該路由是否需要快取,二是從相關路由進入時才進行快取的特定路由陣列。
  4. 在beforeEach進行設定,每次進入路由之前,對進入的路由及其所有父級路由進行判斷,若需要快取且命中特定路由陣列,則將相關路由新增至快取配置檔案中;若不符合,則將相關路由刪除。(此步驟實現了路由切換時,需要則讀取快取,不需要則重新獲取資料。)
  5. 使用全域性mixin,進入相關元件之前,對當前路由進行判斷,如果需要快取的則將該路由新增至快取配置中。(此步驟實現了快取當前開啟的需要快取的頁面。)

具體實現

1. 使用include屬性控制路由快取

此處需要注意的是,include匹配首先檢查元件自身的 name 選項,如果 name 選項不可用,則匹配它的區域性註冊名稱 (父元件 components 選項的鍵值)。匿名元件不能被匹配。

但是vue-router的環境下,是沒有區域性註冊名稱的,只能為元件補全name屬性。

因此,請務必給元件新增 name 選項,否則匿名元件將全部應用快取。


<keep-alive :include="$store.state.cachedRouteNames">
  <router-view />
</keep-alive>

複製程式碼

2. 新增全域性路由快取配置


// store/index.js

const store = new vuex.Store({
    state: {
        // 快取的路由列表
        cachedRouteNames: [],
    },
    mutations: {
        UPDATE_CACHEDROUTENAMES(state,{ action, route }) {
          const methods = {
            'add': () => {
              state.cachedRouteNames.push(route)
            },
            'delete': () => {
              state.cachedRouteNames.splice(state.cachedRouteNames.findIndex((e) => { return e === route}),1)
            }
          }
          methods[action]()
        }
    }
})

複製程式碼

3. 配置路由元資訊,對需要快取的路由進行配置

keepAlive表明路由需要被快取,必須,否則不快取

cacheWhenFromRoutes為陣列,非必須,若為falsy值,則任何時候均快取;若為空陣列,則任何時候均不快取


// router/index.js

{
    path: '/productslist',
    name: 'ProductsList',
    component: ProductsList,
    meta: {
        keepAlive: true,
        cacheWhenFromRoutes: ['ProductDetail']  // 此處配置的是路由的name
    }
},

複製程式碼

4. 配置全域性前置守衛,按需讀取快取


// routeControl.js

// 需要快取的路由名稱陣列
const cachedRouteNames = store.state.cachedRouteNames;

// 定義新增快取元件name函式,設定的是元件的name
const addRoutes = (route) => {
  const routeName = route.components.default.name
    if (routeName && cachedRouteNames.indexOf(routeName) === -1) {
    store.commit('UPDATE_CACHEDROUTENAMES', { action: 'add', route: routeName })
  }
}

// 定義刪除快取元件name函式,設定的是元件的name
const deleteRoutes = (route) => {
  const routeName = route.components.default.name
  if (routeName && cachedRouteNames.indexOf(routeName) !== -1) {
    store.commit('UPDATE_CACHEDROUTENAMES', { action: 'delete', route: routeName })
  }
}

router.beforeEach((to, from, next) => {
  
  // 處理快取路由開始
  // 在讀取快取之前,先對該元件是否讀取快取進行處理
  to.matched.forEach((item, index) => {
    const routes = item.meta.cacheWhenFromRoutes;
    /**
     * 此處有幾種情況
     *  1. 沒有配置cacheWhenFromRoutes, 則一直快取;
     *  2. 配置了cacheWhenFromRoutes,但是首次開啟此web app,則from.name為空,此時應該將該頁面元件的name新增到快取配置檔案中
     *  3. 配置了cacheWhenFromRoutes,from.name不為空,若命中cacheWhenFromRoutes,則新增該頁面元件的name到快取配置檔案中,否則刪除。
     *
     **/
    if (item.meta.keepAlive && (!routes || (routes && (!from.name || routes.indexOf(from.name) !== -1)))) {
      addRoutes(item)
    } else {
      deleteRoutes(item)
    }
    
  })
  // 處理快取路由結束

  new Promise(( resolve, reject ) => {
    // ..other codes
  }).then( res => {
    if ( res ) {
      next(res)
    } else {
      next()
    }
  })
})

// 全域性混入。此步驟的目的是在該元件被解析之後,若是屬於需要快取的元件,先將其新增到快取配置中,進行快取。

// 導航守衛的最後一個步驟就是呼叫 beforeRouteEnter 守衛中傳給 next 的回撥函式,此時整個元件已經被解析,DOM也已經更新。

Vue.mixin({
  beforeRouteEnter(to, from, next) {
    next(vm => {
      to.matched.forEach((item) => {
        const routeName = item.components.default.name
        if (to.meta.keepAlive && routeName && cachedRouteNames.indexOf(routeName) === -1) {
          store.commit('UPDATE_CACHEDROUTENAMES', { action: 'add', route: routeName })
        }
      })
    })
  },
})

複製程式碼

寫在最後

坑點

  1. 此方案涉及兩個name,一個是設定特定路由時,使用路由的name。另一個是動態生成快取配置檔案時,使用的是頁面元件的name。
  2. 務必給元件新增name屬性,便於include屬性的使用,也方便除錯跟蹤。如果元件缺少name屬性,將會預設使用快取。
  3. 動態處理快取配置時,一定要對to.matched進行遍歷,否則巢狀路由的父級路由的快取就無法生效,將導致子路由的快取也無法生效。
  4. 全域性混入有一定危險性,慎用...

以上是實踐過程中摸索出來的一種解決方案,我相信存在更加優雅高效的解決方式。如果你正好實踐過相關方法,煩請指正,謝謝。

更多參考

github.com/vuejs/vue/i…

相關文章