Vue 全站快取之 vue-router-then 實現原理

wanyaxing發表於2018-08-01

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

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

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

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

前言

就效果而言,我很滿意v-model-link指令帶來的開發效率的提升,不過 vue-router-then 這個外掛本身的程式碼實現得有點粗暴,總覺得自己對 vue 的理解還是比較膚淺,有時候看別人家的文章總有不明覺厲的感嘆,然而正如我在知乎專欄在部落格的自我介紹裡所說,高樓大廈平地起,我們也想加上一塊磚,自己這三板斧該獻醜還是獻醜,拋磚引玉也行的。

活著才有 DPS

從第一篇文章Vue 全站快取之 keep-alive : 動態移除快取開始,我們就一直在強調如何去實現頁面快取,vue-router-then 正是建立在頁面快取成功的基礎之上的功能,如果你不能正確的使用頁面快取功能,或者說頁面快取被銷燬了,vue-router-then 也就沒有意義了。

實現原理

這個外掛的原理說開了實現得非常粗暴,說白了就是一句話:在路由跳轉事件裡給新頁面元件綁事件。

返回一個 promise

在this.$router系列方法的基礎之上,包裝一個 promise ,並將 resolve 方法給儲存起來。

const routerThen = {
        '$router':null,
        resolve:null,
        //跳到指定頁面,並返回promise
        request:function(requestType='push', location, onComplete=null, onAbort=null){
            if (!location || location=='')
            {
                throw new Error('location is missing');
            }
            return new Promise( (resolve, reject)=>{
                if (this.$router)
                {
                    console.log('this.$router',this.$router);
                    this.resolve = resolve;
                    switch (requestType)
                    {
                        case 'push':
                            this.$router.push(location, onComplete, onAbort);
                            break;
                        case 'replace':
                            this.$router.replace(location, onComplete, onAbort);
                            break;
                        case 'go':
                            this.$router.go(location);
                            break;
                        default:
                            reject('requestType error:'+requestType);
                            break;
                    }
                }
                else
                {
                    reject('$router missing');
                }
            }).catch(error=>{
                this.resolve = null;
                throw new Error(error);
            });
        },
複製程式碼

在路由事件裡夾點私貨

上文裡,將 resolve 存好後,頁面就應該開始跳轉了,此時可以捕捉路由事件,在新頁面載入後,將新頁面物件 vm回撥給 promise 。

        Vue.mixin({
            // 在路由跳轉到下一個頁面之前,為下一個頁面註冊回撥事件。
            beforeRouteEnter:function(to, from, next){
                if (routerThen.resolve)
                {
                    next(vm=>{
                            routerThen.resolve(vm);
                            routerThen.resolve = null;
                    });
                }
                else
                {
                    next();
                }
            },
            beforeRouteUpdate:function(to, from, next){
                if (routerThen.resolve)
                {
                    routerThen.resolve(this);
                    routerThen.resolve = null;
                }
                next();
            },
        });
複製程式碼

拿到頁面物件啥都好辦了

比如,modelLink方法,其實就是拿到 vm 物件給它塞了個 input 事件。

        modelLink:function(link, el=null){
            return this.push(link).then(vm=>{
                vm.$once('input',value=>{
                    if (typeof el == 'function')
                    {
                        el(value);
                    }
                    else if (typeof el == 'object')
                    {
                        if (el.$emit)
                        {
                            el.$emit('input',value);
                        }
                        else if (el.tagName)
                        {
                            el.value = value;
                            const e = document.createEvent('HTMLEvents');
                            // e.initEvent(binding.modifiers.lazy?'change':'input', true, true);
                            e.initEvent('input', true, true);
                            el.dispatchEvent(e);
                        }
                    }
                });
                return vm;
            })
        },
複製程式碼

v-model-link 只是一個語法糖

我很喜歡語法糖這個概念,複雜的事簡單化。

        clickElFun:function(event){
            let link = this.getAttribute('model-link');
            if (link)
            {
                console.log(this);
                return routerThen.modelLink(link,this.vnode && this.vnode.componentInstance?this.vnode.componentInstance:this);
            }
            return Promise.resolve();
        },

    Vue.directive('model-link',  function (el, binding, vnode) {
            el.binding = binding;
            el.vnode   = vnode;
            el.setAttribute('model-link',binding.value);
            el.removeEventListener('click',routerThen.clickElFun);
            el.addEventListener('click',routerThen.clickElFun);
        });
複製程式碼

後語

還是那句話,這個程式碼有點粗暴,作拋磚引玉,供大家參考。

我很喜歡全站快取這個理念,在目前 vue 社群裡類似的文章很少看到,希望能有更多朋友參與進來,挖掘其中的亮點。

END

我最近還在研究全站快取情況下跨多級頁面的資料共用和更新的情況,感覺會很有意思,敬請期待,這次時間需要久一點。

原文來自阿星的部落格:wanyaxing.com/blog/201807…

相關文章