Vue項目全局配置頁面緩存,實現按需讀取緩存

這篇文章也發在個人博客,歡迎圍觀😄前端

寫在前面

一個web app的實際使用場景中,有一些情景的交互要求,是記錄用戶的瀏覽狀態的。最多見的就是在列表頁進入詳情頁以後,再返回到列表頁,用戶但願返回到進入詳情頁以前的狀態繼續操做。可是有些使用場景,用戶又是但願可以獲取最新的數據,例如同級列表頁之間切換的時候。vue

如此,針對上述兩種使用場景,須要實現按需讀取頁面緩存。因爲SPA應用的路由邏輯也是在前端實現的,所以能夠在前端對路由的邏輯進行設置以實現所需效果。git

使用技術

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

整體思路

keep-alive判斷當前組件是否讀取緩存的節點,在整個生命週期裏面很是靠後,在afterEach以後,基本在組件實例建立以前。(所以在此以前對當前組件是否讀取緩存進行處理都是可行的,我選擇在全局前置守衛進行處理)github

而判斷當前組件是否緩存的節點,則早於組件的beforeRouteLeave鉤子。web

基於上述邏輯,本方案解決的邏輯是,對當前打開的頁面進行判斷,動態生成須要keepAlive的組件數組配置,對有可能須要緩存的先行進行緩存,而後在每次路由切換的時候,再進行判斷,按需讀取頁面緩存。vue-router

  1. 使用kepp-alive進行緩存,使用include屬性對須要緩存的頁面進行配置。
  2. 因爲須要緩存的頁面配置系動態生成,因此使用vuex儲存該配置。
  3. 在路由元信息中寫入兩個配置,一是該路由是否須要緩存,二是從相關路由進入時才進行緩存的特定路由數組。
  4. 在beforeEach進行設置,每次進入路由以前,對進入的路由及其全部父級路由進行判斷,若須要緩存且命中特定路由數組,則將相關路由添加至緩存配置文件中;若不符合,則將相關路由刪除。(此步驟實現了路由切換時,須要則讀取緩存,不須要則從新獲取數據。)
  5. 使用全局mixin,進入相關組件以前,對當前路由進行判斷,若是須要緩存的則將該路由添加至緩存配置中。(此步驟實現了緩存當前打開的須要緩存的頁面。)

具體實現

1. 使用include屬性控制路由緩存

此處須要注意的是,include匹配首先檢查組件自身的 name 選項,若是 name 選項不可用,則匹配它的局部註冊名稱 (父組件 components 選項的鍵值)。匿名組件不能被匹配。vuex

可是vue-router的環境下,是沒有局部註冊名稱的,只能爲組件補全name屬性。api

所以,請務必給組件添加 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…

相關文章
相關標籤/搜索