前端webapp應用爲了追求相似於native模式的細緻體驗,老是在不斷的在向native的體驗靠攏;好比本文即將要說到的功能,native因爲是多頁應用,新頁面能夠啓用一個的新的webview來打開,後退實際上是關閉當前webview,其上一個webview就天然顯示出來;可是在單頁的webapp應用中,全部內容實際上是在一個頁面中展現的,不存在多頁的狀況,這時就須要前端開發來想辦法實現相應的體驗效果。javascript
首先須要說明一下,本文所說的前進刷新後退不刷新是指組件是否從新渲染,好比列表A頁面,點擊其中的每一項進入詳情B頁面,而後從B頁面後退到列表A頁面時,A頁面沒有從新渲染,也沒有從新發送ajax請求。下面,咱們就來講說在vue的單頁應用中,實現前進刷新後退不刷新的一些實現方案,其餘的方案你們能夠一塊兒補充。html
keep-alive是vue官方提供的一種緩存組件實例的方法,vue官網對其用法的介紹:前端
<keep-alive>
包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們。vue
正如vue官網的介紹,咱們在開發中就可使用他這一點來緩存後退不用刷新的路由組件。具體的實現思路以下。java
在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 // 此組件須要被緩存 } } ]
activated
鉤子函數實現數據更新邏輯須要強調的是keep-alive組件(這裏是指keep-alive包裹的路由組件,下同)與一個vue組件是有區別的,vue的具體生命週期函數能夠參考這裏;而keep-alive
組件,除了正常vue組件提供的生命週期以外,其額外新增了2個跟keep-alive
相關的鉤子函數:vue-router
既然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實現後推不刷新的方案,有一些地方須要特別注意:
意思就是在開發過程當中須要知道後退不刷新組件雖然不從新渲染,可是要知道組件數據在什麼狀況下須要從新發送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 },
繼續以上面的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來緩存組件時,因爲其是緩存在內存中的,若不加處理,內存堆積愈來愈大,致使系統卡頓。正確的解決方案是:須要及時銷燬掉內存緩存的組件。
具體能夠參考: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 } ] }
<div class="safe-area-pb"> <purchase /> <router-view /> </div>
這樣,經過下單頁進入其餘頁面好比進入修改聯繫人信息頁面,那麼路由從/order進入到/order/contact,修改完成後回退會回到父路由/order中,完成後推不刷新的功能。
固然,正如上面所說的,嵌套路由方案只是一種可選擇方案,有其對應的使用場景;另外,使用過程還須要注意如下幾點:
**一、進入子路由後,如果在子路由強制刷新後,父子路由的組件都會從新渲染,執行各自路由組件的生命週期;父路由中設置相關邏輯都會執行。
**二、子路由若被其餘頁面共用,這時進入子路由時會觸發第一點的狀況,因此最好子路由是父路由獨佔的。
這種方案主要是利用vue提供的動態路由組件component來實現,頁面組件的切換再也不根據路由path來決定,而是根據不一樣的業務邏輯加載不一樣的動態組件。具體的實現能夠參考這篇文章解決方案第6點部分:異步加載的業務線如何動態註冊路由?。一樣,同步路由也可使用動態路由來完成對應後退不刷新功能。這不過這種方式的使用場景更急侷限。
上面提供的3種解決方案,第一種方案你們都比較熟悉,後面兩種可能相對來講就比較陌生。它們只是解決同一問題的不一樣解決方案,想必還有其餘的解決方案本人沒有想到,有其餘更好方案的能夠一塊兒探討。
一、滴滴 webapp 5.0 Vue 2.0 重構經驗分享
二、另闢蹊徑:vue單頁面,多路由,前進刷新,後退不刷新
三、vue-router 之 keep-alive
四、記一次vue 的keep-alive踩坑之路
五、但願keep-alive能增長能夠動態刪除已緩存組件的功能