vue單頁應用前進刷新後退不刷新方案探討

引言

前端webapp應用爲了追求相似於native模式的細緻體驗,老是在不斷的在向native的體驗靠攏;好比本文即將要說到的功能,native因爲是多頁應用,新頁面能夠啓用一個的新的webview來打開,後退實際上是關閉當前webview,其上一個webview就天然顯示出來;可是在單頁的webapp應用中,全部內容實際上是在一個頁面中展現的,不存在多頁的狀況,這時就須要前端開發來想辦法實現相應的體驗效果。javascript

首先須要說明一下,本文所說的前進刷新後退不刷新是指組件是否從新渲染,好比列表A頁面,點擊其中的每一項進入詳情B頁面,而後從B頁面後退到列表A頁面時,A頁面沒有從新渲染,也沒有從新發送ajax請求。下面,咱們就來講說在vue的單頁應用中,實現前進刷新後退不刷新的一些實現方案,其餘的方案你們能夠一塊兒補充。html

keep-alive方案

keep-alive是vue官方提供的一種緩存組件實例的方法,vue官網對其用法的介紹:前端

<keep-alive> 包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們。vue

正如vue官網的介紹,咱們在開發中就可使用他這一點來緩存後退不用刷新的路由組件。具體的實現思路以下。java

一、模板中使用keep-alive來緩存對應的路由組件

在app.vue模板中改寫<router-view>,具體能夠這樣:git

<keep-alive>
    <router-view v-if="$route.meta.keepAlive">
        <!-- 這裏是會被緩存的視圖組件,好比列表A頁面 -->
    </router-view>
</keep-alive>

<router-view v-if="!$route.meta.keepAlive">
    <!-- 這裏是不被緩存的視圖組件,好比詳情B頁面-->
</router-view>

這種方式須要經過vue路由元信息的配合,固然也能夠像下面這樣:github

<keep-alive include="A">
    <router-view>
        <!-- 只有路徑匹配到的視圖組件,如上面的列表A頁面會被緩存! -->
    </router-view>
</keep-alive>

這種方式缺點是:web

須要事先知道路由組件的**name**值,這在大型項目中不是一個特別好的選擇。

二、在路由配置文件中配置路由元信息

下面以第一種模板方式來展開介紹。對應上面模板文件中的路由元數據配置以下:ajax

routes: [{
        path: '/',
        name: 'home',
        component: Home,
        meta: {
            keepAlive: false //此組件不須要被緩存
        }
    },
    {
        path: '/list',
        name: 'list',
        component: List,
        meta: {
            keepAlive: true //此組件須要被緩存
        }
    },
    {
        path: '/detail',
        name: 'detail',
        component: Detail,
        meta: {
            keepAlive: false // 此組件須要被緩存
        }
    }
]

三、在keep-alive組件提供activated鉤子函數實現數據更新邏輯

須要強調的是keep-alive組件(這裏是指keep-alive包裹的路由組件,下同)與一個vue組件是有區別的,vue的具體生命週期函數能夠參考這裏;而keep-alive組件,除了正常vue組件提供的生命週期以外,其額外新增了2個跟keep-alive相關的鉤子函數:vue-router

  • activated: 緩存的組件再次進入時會觸發
  • deactivated: 緩存的組件離開時會觸發

既然keep-alive組件提供了這麼多生命週期函數鉤子,那麼這些鉤子函數具體的執行順序是怎樣的呢?

第一次進入keep-alive組件時,其生命週期執行順序:

beforeRouteEnter --> created --> mounted --> activated --> deactivated

非首次進入時,其生命週期執行順序:

beforeRouteEnter -->activated --> deactivated

能夠看到,非首次進入keep-alive組件時,正常的vue組件生命週期函數是不會在執行,而會執行keep-alive新增的兩個週期鉤子函數。同時也能夠看出離開keep-alive組件時其destroy周期函數並無執行,從側面證實緩存組件並無銷燬。根據介紹,咱們能夠:

經過利用keep-alive提供activated鉤子函數來決定是否進行ajax請求來更新組件,以及deactivated鉤子函數來重置頁面相關狀態。

keep-alive實現後推不刷新的方案,有一些地方須要特別注意:

  • keep-alive組件的更新時機要有清晰的認知

意思就是在開發過程當中須要知道後退不刷新組件雖然不從新渲染,可是要知道組件數據在什麼狀況下須要從新發送ajax請求來獲取數據,從而更新組件。

就拿上面的A、B頁面來講,咱們須要知道列表A頁面對應的keep-alive組件在何時進行更新,由於進入A頁面的入口能夠是從B頁面後退而來,也可能從其餘頁面前進而來;固然須要對這兩種不一樣狀況須要加以區分,不然A頁面的數據就一直是第一次緩存過的數據。

這篇文章給出了一種解決方案:

首先,在每一個路由元信息meta中添加一個isBack字段,用來解決beforeRouterEnter不能直接訪問vue實例。

...
    {
        path: '/list',
        name: 'list',
        component: List,
        meta: {
            keepAlive: true, //此組件須要被緩存
            isBack: false
        }
    }
    ...

而後,藉助beforeRouteEnter鉤子函數來判斷頁面來源:

beforeRouteEnter(to, from, next) {
      if(from.name === 'detail') { //判斷是從哪一個路由過來的,如果detail頁面不須要刷新獲取新數據,直接用以前緩存的數據便可
          to.meta.isBack = true;
      }
      next();
    },

最後,須要藉助keep-alive提供鉤子函數activated來完成是否更新:

activated() {
    if(!this.$route.meta.isBack) {
      // 若是isBack是false,代表須要獲取新數據,不然就再也不請求,直接使用緩存的數據
      this.getData(); // ajax獲取數據方法
    }
    // 恢復成默認的false,避免isBack一直是true,致使下次沒法獲取數據
    this.$route.meta.isBack = false
  },
  • keep-alive組件前進的頁面刷新致使keep-alive組件狀態丟失

繼續以上面的A、B頁面爲例,在進入詳情B頁面後,而後刷新,這時列表A頁面的緩存的數據都丟失了,因爲上面的判斷規則也會致使不會從新獲取數據。因此對於這種問題,還須要額外加一些判斷條件。因爲keep-alive第一次進入時會執行created方法,因此利用這點加一個標識來加以判斷:

//第一次進入keep-alive路由組件時
    created() {
      this.isFirstEnter = true;
     // 只有第一次進入或者刷新頁面後纔會執行此鉤子函數,使用keep-alive後(2+次)進入不會再執行此鉤子函數
   },

activated鉤子函數也須要增長對應的判斷:

activated() {
     if(!this.$route.meta.isBack || this.isFirstEnter){
         // 若是isBack是false,代表須要獲取新數據,不然就再也不請求,直接使用緩存的數據
         // 若是isFirstEnter是true,代表是第一次進入此頁面或用戶刷新了頁面,需獲取新數據
         this.data = ''// 把數據清空,能夠稍微避免讓用戶看到以前緩存的數據
         this.getData();
     }
     // 恢復成默認的false,避免isBack一直是true,致使下次沒法獲取數據
     this.$route.meta.isBack=false
     // 恢復成默認的false,避免isBack一直是true,致使每次都獲取新數據
     this.isFirstEnter=false;
   },
  • 緩存過多keep-alive組件,因常駐內存會致使內存佔用過多

這是一個特別須要注意的問題,尤爲是當整個系統或者系統大部分頁面都使用keep-alive來緩存組件時,因爲其是緩存在內存中的,若不加處理,內存堆積愈來愈大,致使系統卡頓。正確的解決方案是:須要及時銷燬掉內存緩存的組件

具體能夠參考:vue issue#6509記一次vue 的keep-alive踩坑之路兩篇文章的實現思路。

嵌套路由

嵌套路由具體的實現能夠參考官網,這種方案也是一種解決思路。下面以一個具體的例子(以下圖所示)來講一下實現的具體過程。

正如上圖所示,一個下單頁面有6處跳出當前頁面查看規則、協議或者修改具體某些內容的頁面,由於這6項依賴這個訂單頁,那麼可使用路由嵌套來實現這種後退不刷新的過程,下單頁做爲父路由,其餘跳轉項能夠做爲其子路由。具體步驟:

一、配置路由信息

{
      path: '/order',
      component: Order,
      children: [
        {
          path: 'invoice',
          component: Invoice
        }, {
          path: 'contact',
          component: Contact
        },
        {
          path: 'costrule',
          component: CostRule
        }, {
          path: 'refundrule',
          component: RefundRule
        },{
          path: 'useragreement',
          component: UserAgreement
        },{
          path: 'payrule',
          component: PayRule
        }
      ]
    }

二、在下單頁Order組件模板中配置路由嵌套。

<div class="safe-area-pb">
     <purchase />
     <router-view />    
    </div>

這樣,經過下單頁進入其餘頁面好比進入修改聯繫人信息頁面,那麼路由從/order進入到/order/contact,修改完成後回退會回到父路由/order中,完成後推不刷新的功能。

固然,正如上面所說的,嵌套路由方案只是一種可選擇方案,有其對應的使用場景;另外,使用過程還須要注意如下幾點:

**一、進入子路由後,如果在子路由強制刷新後,父子路由的組件都會從新渲染,執行各自路由組件的生命週期;父路由中設置相關邏輯都會執行。

**二、子路由若被其餘頁面共用,這時進入子路由時會觸發第一點的狀況,因此最好子路由是父路由獨佔的。

component組件配合路由方案

這種方案主要是利用vue提供的動態路由組件component來實現,頁面組件的切換再也不根據路由path來決定,而是根據不一樣的業務邏輯加載不一樣的動態組件。具體的實現能夠參考這篇文章解決方案第6點部分:異步加載的業務線如何動態註冊路由?。一樣,同步路由也可使用動態路由來完成對應後退不刷新功能。這不過這種方式的使用場景更急侷限。

總結

上面提供的3種解決方案,第一種方案你們都比較熟悉,後面兩種可能相對來講就比較陌生。它們只是解決同一問題的不一樣解決方案,想必還有其餘的解決方案本人沒有想到,有其餘更好方案的能夠一塊兒探討。

參考

一、滴滴 webapp 5.0 Vue 2.0 重構經驗分享
二、另闢蹊徑:vue單頁面,多路由,前進刷新,後退不刷新
三、vue-router 之 keep-alive
四、記一次vue 的keep-alive踩坑之路
五、但願keep-alive能增長能夠動態刪除已緩存組件的功能

相關文章
相關標籤/搜索