Vue 全站快取二:如何設計全站快取

wanyaxing發表於2018-08-01

在前文Vue 全站快取之 keep-alive : 動態移除快取中,我們實現了在路由離開時動態移除快取這一功能,以此為基礎,vue 全站使用快取成為可能。

本文長篇大論囉裡囉嗦巴拉巴拉,請跳著讀、反覆讀、隨機讀、躺著讀……

系列篇1:Vue 全站快取之 keep-alive : 動態移除快取

本篇為系列篇2:Vue 全站快取二:如何設計全站快取

系列篇3:Vue 全站快取之 vue-router-then :前後頁資料傳遞

系列篇4:Vue 全站快取之 vue-router-then :實現原理

前言

從早期粗暴得將 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>
複製程式碼

為什麼要使用快取

最常見的一個場景是新建訂單時選擇地址,新建訂單是一個路由頁面,去選擇使用者現有的地址又是一個路由頁面,所以理所當然的,我們希望使用者選擇完地址回到訂單頁面的時候,訂單裡的其他資料比如選擇的優惠券啊收件日期啊都能繼續保持。

Vue 全站快取二:如何設計全站快取

在大量類似的場景裡,不斷的出現資料保留和複用的需求,所以 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…

相關文章