在前文Vue 全站快取之 keep-alive : 動態移除快取中,我們實現了
在路由離開時動態移除快取
這一功能,以此為基礎,vue 全站使用快取成為可能。
本文長篇大論囉裡囉嗦巴拉巴拉,請跳著讀、反覆讀、隨機讀、躺著讀……
本篇為系列篇2:Vue 全站快取二:如何設計全站快取
前言
從早期粗暴得將 css、js 資源設定瀏覽器本地快取,到後來小圖示合併成大圖節省請求資源,還有動態請求304狀態判斷,然後 ajax 開啟 web2.0 時代, pjax 大放光彩,到如今 vue.js 等前端框架的繁榮盛世,所有的這一系列發展,我認為,提速
是一個核心驅動力。
keep-alive
在 vue 裡,支援 keep-alive 特性,通過 keep-alive ,不再銷燬舊元件為新元件讓路,而是快取起來以待將來複用,當複用快取元件時,如果資料沒有變化甚至可以直接原樣恢復。連結
keep-alive 和 vue-router
將 router-view 放置到 keep-alive 中,即可粗暴的實現所有路由頁的快取功能。
<!-- App.vue -->
<keep-alive><router-view class="transit-view"></router-view></keep-alive>
複製程式碼
為什麼要使用快取
最常見的一個場景是新建訂單時選擇地址
,新建訂單是一個路由頁面,去選擇使用者現有的地址又是一個路由頁面,所以理所當然的,我們希望使用者選擇完地址回到訂單頁面的時候,訂單裡的其他資料比如選擇的優惠券啊收件日期啊都能繼續保持。
在大量類似的場景裡,不斷的出現資料保留和複用的需求,所以 vuex 出現了,通過第三方的一個公共元件來儲存和中轉資料,這是一個解決方案,而且還是主流的解決方案哦。
然而換個角度,如果訂單頁在選擇地址的時候被快取了,回到訂單頁後直接複用前面的訂單元件,其他資料都保留此時只要更新下地址資料,讓所有的程式碼邏輯都集中在訂單元件之中,這樣的開發體驗是不是會更直觀更友好?
這是見仁見智的思路,各有想法,不好說誰好誰壞,我們就先繼續討論快取元件的方案吧。
出現了一點小問題
如果所有的路由頁都被快取了,那麼當你不想使用快取的時候怎麼辦?比如又建了一個新訂單,進入不同文章的編輯元件,比如進入不同的使用者中心,快取固然提了速,有時我們也會不想要快取功能,特別是一些表單場景,我們既希望填寫一半進入下一頁面時能保留填寫的資料,我們又希望新進入的表單是一個全新的表單頁。
魚和熊掌可以兼得
我們既希望填寫一半進入下一頁面時能保留填寫的資料,我們又希望新進入的表單是一個全新的表單頁。
,
換句話說,回到上一個頁面時使用快取,進入下一個頁面時不使用快取
,
再換句話說,所有頁面都用快取,只在後退(回到上一頁)時移除當前頁快取,這樣下一次前進(進入當前頁)時因為沒有快取就自然使用全新頁面
,
也就是說,只要實現後退(回到上一頁)時移除當前頁快取
這個功能,就可以了。
在路由中定義位置
這是一種快取複用的思路,為了實現後退(回到上一頁)時移除當前頁快取
,因為想要實現動態確定使用者的前進後退行為比較麻煩,所以,我們有個傻瓜式的方案:預測使用場景約定各路由頁面的層級關係。
比如,在 routes 定義裡,我們可以這麼定義各路由頁:
// 僅供參考,此處缺少路由元件定義
// router/index.js
routes: [
{ path: '/', redirect:'/yingshou', },
{ path: '/yingshou', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/contract_list', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/customer', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/wode', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/yingfu', meta:{rank:1.5,isShowFooter:true}, },
{ path: '/yingfu/pact_list', meta:{rank:2.5}, },
{ path: '/yingfu/pact_detail', meta:{rank:3.5}, },
{ path: '/yingfu/expend_view', meta:{rank:4.5}, },
{ path: '/yingfu/jizhichu', meta:{rank:5.5}, },
{ path: '/yingfu/select_pact', meta:{rank:6.5}, },
{ path: '/yingfu/jiyingfu', meta:{rank:7.5}, },
]
複製程式碼
核心的思路是,在定義路由時,在 meta 中定義一個 rank 欄位來宣告該路由的頁面優先順序, 比如 1.5 標識第 1 層如首頁,2.5 表示第 2 層如商品列表頁, 3.5標識第 3 層商品詳情頁,以此類推。
如果大家同在一層,也可以通過 1.4 和 1.5 這樣小數位來約定先後層級。
總之,我們期望的是,從第1層進入第2層是前進,從第3層回到第2層是後退。
在路由跳轉裡動態判斷移除快取
使用Vue.mixin的方法攔截了路由離開事件,並在該攔截方法中實現後退時銷燬頁面快取
。
// main.js
Vue.mixin({
beforeRouteLeave:function(to, from, next){
if (from && from.meta.rank && to.meta.rank && from.meta.rank>to.meta.rank)
{//此處判斷是如果返回上一層,你可以根據自己的業務更改此處的判斷邏輯,酌情決定是否摧毀本層快取。
if (this.$vnode && this.$vnode.data.keepAlive)
{
if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache)
{
if (this.$vnode.componentOptions)
{
var key = this.$vnode.key == null
? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
: this.$vnode.key;
var cache = this.$vnode.parent.componentInstance.cache;
var keys = this.$vnode.parent.componentInstance.keys;
if (cache[key])
{
if (keys.length) {
var index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
}
delete cache[key];
}
}
}
}
this.$destroy();
}
next();
},
});
複製程式碼
這樣就行了嗎
不行,這樣只解決了動態移除快取
的情況,讓使用者既可以進入新頁面,也可以回到舊頁面。前者進新頁面問題不大,後者回到舊頁面這件事我們還沒討論過呢。
快取是說用就用的嗎
即使是路由頁面複用了快取,也只是複用了快取的元件和資料,在實際場景中,從列表 A 進入詳情 B 再進入列表 C ,請問 列表 C 和列表 A 是同一個路由頁,但他們的資料會一樣嗎?應該一樣嗎?
所以就算是快取了也要更新資料?
看起來,我們得到了一個新結論,快取頁的資料也不可靠啊,摔,這日子快沒法過了。
應該有辦法的。
快取的元件被複用時會觸發 activated 事件,非快取元件則會在建立時觸發 created mounted 等一大堆事件,而同一個頁面列表 A 進列表 B,因為 url 引數不同,也會觸發beforeRouteUpdate事件。連結1 連結2 連結3
看起來,我們能夠通過捕捉這些事件乾點啥。
不對不對
第一直覺是,我們在捕捉到頁面載入的事件後去拉取資料更新頁面。仔細一想,我們上面羅裡吧嗦廢了老半天勁是為了進入快取頁面不再看那 loading 條的啊。摔,怎麼又繞回來了。
笨辦法
這裡提供一個笨辦法,事件該捕捉我們還是要捕捉,只是這是否去做拉取資料這個動作,我們可以有待商榷。
- 在所有的路由頁統一註冊一個方法,就叫pageenter吧,不管從啥情況進來的,我們都去觸發這個方法。
// list.vue
data(){
return {
params : null,
}
},
methods:{
pageenter:function(){
this.params = {
'uuid' : this.$route.query.uuid ,
};
},
}
複製程式碼
- 如果直接 watch 這個 params ,這事還是沒法玩(請想下為什麼?),這裡可以用一個笨辦法,我們將它轉化成字串再 watch,捕捉到字串變化再去重新拉取資料,如果字串沒有變化,則啥也不做。
// list.vue
computed:{
paramsToString(){
return JSON.stringify(this.params);
},
},
watch:{
paramsToString:function(){
this.loadContent();
},
},
methods:{
loadContent:function(){
//此處拉取資料
//this.$http.get(....)
},
}
複製程式碼
- 在main.js裡,可以用Vue.mixin攔截上文提到的三種事件,來觸發 pageenter 方法。
//main.js
Vue.mixin({
/*初始化元件時,觸發pageenter方法*/
mounted:function(){
if (this.pageenter)
{
this.pageenter();
}
},
// /*從其他元件返回啟用當前元件時*/
activated:function(){
if (this.pageenter)
{
this.pageenter();
}
},
/*在同一元件中,切換路由(引數變化)時*/
beforeRouteUpdate:function(to, from, next){
if (this.pageenter)
{
this.$nextTick(()=>{
this.pageenter();
});
}
next();
},
});
複製程式碼
後語
至此,全站快取的框架算是基本搭好了
嗎?
請繼續閱讀-系列篇3:Vue 全站快取之 vue-router-then :前後頁資料傳遞
原文來自阿星的部落格:wanyaxing.com/blog/201807…