1. 程式人生 > >vue單頁應用前進刷新後退不刷新方案探討

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

nested 規則 meta route 獲取 事先 ejs 啟用 ive

引言

前端webapp應用為了追求類似於native模式的細致體驗,總是在不斷的在向native的體驗靠攏;比如本文即將要說到的功能,native由於是多頁應用,新頁面可以啟用一個的新的webview來打開,後退其實是關閉當前webview,其上一個webview就自然顯示出來;但是在單頁的webapp應用中,所有內容其實是在一個頁面中展示的,不存在多頁的情況,這時就需要前端開發來想辦法實現相應的體驗效果。

首先需要說明一下,本文所說的前進刷新後退不刷新是指組件是否重新渲染,比如列表A頁面,點擊其中的每一項進入詳情B頁面,然後從B頁面後退到列表A頁面時,A頁面沒有重新渲染,也沒有重新發送ajax請求。下面,我們就來說說在vue的單頁應用中,實現前進刷新後退不刷新的一些實現方案,其他的方案大家可以一起補充。

keep-alive方案

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

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

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

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

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

<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路由元信息的配合,當然也可以像下面這樣:

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

這種方式缺點是:

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

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

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

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 // 此組件需要被緩存
        }
    }
]

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

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

  • 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項依賴這個訂單頁,那麽可以使用路由嵌套來實現這種後退不刷新的過程,下單頁作為父路由,其他跳轉項可以作為其子路由。具體步驟:

1、配置路由信息

    {
      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
        }
      ]
    }

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

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

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

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

**1、進入子路由後,若是在子路由強制刷新後,父子路由的組件都會重新渲染,執行各自路由組件的生命周期;父路由中設置相關邏輯都會執行。

**2、子路由若被其他頁面共用,這時進入子路由時會觸發第一點的情況,所以最好子路由是父路由獨占的。

component組件配合路由方案

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

總結

上面提供的3種解決方案,第一種方案大家都比較熟悉,後面兩種可能相對來說就比較陌生。它們只是解決同一問題的不同解決方案,想必還有其他的解決方案本人沒有想到,有其他更好方案的可以一起探討。

參考

1、滴滴 webapp 5.0 Vue 2.0 重構經驗分享
2、另辟蹊徑:vue單頁面,多路由,前進刷新,後退不刷新
3、vue-router 之 keep-alive
4、記一次vue 的keep-alive踩坑之路
5、希望keep-alive能增加可以動態刪除已緩存組件的功能

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