spa路由簡單實現(xiàn)代碼解析(梁王的代碼解剖室)

前言

本文假設讀者使用過或者了解什么是SPA

SPA路由簡單實現(xiàn)

代碼(被解析項目地址)

為了避免誤會,代碼不是我寫的,我只是解析源碼。
spa-routers
demo演示: demo

解析

解析分3個部分,hash路由,路由規(guī)則注冊,路由同異步加載

hash路由

用過vue之類的框架應該都知道什么是hash,這里說的是url的hash不是數(shù)據(jù)結構里面的東西。
類似這種http://www.example.com/index.html#print

單頁應用當hash變換的時候,切換不同的界面。而瀏覽器提供了一個事件hashchange

function changePart(event) {
    alert('your hash changed');
    console.log(event);
}
window.onhashchange = changePart;

讀者可以自行在控制臺試一下然后改變hash看看。

在項目代碼中,在初始化spaRouters的時候會監(jiān)聽事件

spaRouters.prototype={
    init:function(){
        var self = this;
        //頁面加載匹配路由
        window.addEventListener('load',function(){
            self.urlChange()
        })
        //路由切換
        window.addEventListener('hashchange',function(){
            self.urlChange()
        })
        ...
    },
    ...
}

現(xiàn)在我們來看看urlChange方法,方法在spaRouters的原型里面

    ...
    urlChange:function(){
        var currentHash = util.getParamsUrl();
        if(this.routers[currentHash.path]){
            this.refresh(currentHash)
        }else{
            //不存在的地址重定向到首頁
            location.hash = '/index'
        }
    },
    refresh:function(currentHash){
        var self = this;
        // 提供導航鉤子
        if(self.beforeFun){
            // 這里還可以像vue一樣還提供from
            self.beforeFun({
                to:{
                    path:currentHash.path,
                    query:currentHash.query
                },
                // 關于next我明天會補充一下
                next:function(){
                    self.routers[currentHash.path].callback.call(self,currentHash)
                }
            })
        }else{
            self.routers[currentHash.path].callback.call(self,currentHash)
        }
    },
    ...

hash變更后,看routers里面有沒有hash對應的路由規(guī)則(這里直接用的下標,其實想做得更好可以使用模板,還有用正則來匹配,像vue路由就有類似/users/:id這種)。

如果有匹配的話調用refresh,refresh這里還提供了導航鉤子beforeFun(通過spaRouters原型提供的beforeEach還有afterEach的方法設置),其中的next我后面會再擴展一下。

路由規(guī)則注冊

剛剛講完了hash變化后程序的邏輯,現(xiàn)在我們來看看怎么注冊路由規(guī)則。
同樣是spaRouters的原型里面

    //單層路由注冊
    ...
    map:function(path,callback){
        path = path.replace(/\s*/g,"");//過濾空格
        if(callback && Object.prototype.toString.call(callback) === '[object Function]' ){
            this.routers[path] ={
                callback:callback,//回調
                fn:null //存儲異步文件狀態(tài)
            }
        }else{
            console.trace('注冊'+path+'地址需要提供正確的的注冊回調')
        }
    },
    ...

原型中的map方法注冊路由,并提供一個回調,這個回調也就是上面hashchange的時候會調用的內容,一般是進行同異步加載js。下面是項目內的測試調用

    ...
    spaRouters.map('/index',function(transition){
      spaRouters.asyncFun('js/index.js',transition)
    })
    spaRouters.map('/list',function(transition){
      spaRouters.asyncFun('js/list.js',transition)
    })
    spaRouters.map('/detail',function(transition){
      spaRouters.asyncFun('js/detail.js',transition)
    })
    spaRouters.map('/detail2',function(transition){
      spaRouters.syncFun(function(transition){
        document.getElementById("content").innerHTML = '<p>當前同步渲染詳情頁' + JSON.stringify(transition) +'</p>'
      },transition)
    })
    ...

其中的syncFun和asyncFun我在第三部分講
之前也說了,這里可以優(yōu)化一下使用模板正則匹配(當然使用正則之后又有注冊順序的問題)

同異步加載

同樣是在spaRouters的原型中的兩個方法

    ...
    //路由異步懶加載js文件
    asyncFun:function(file,transition){
       var self = this;
       if(self.routers[transition.path].fn){
            self.afterFun && self.afterFun(transition)
            self.routers[transition.path].fn(transition)
       }else{
           console.log("開始異步下載js文件"+file)
           var _body= document.getElementsByTagName('body')[0];
           var scriptEle= document.createElement('script');
           scriptEle.type= 'text/javascript';
           scriptEle.src= file;
           scriptEle.async = true;
           SPA_RESOLVE_INIT = null;
           scriptEle.onload= function(){
               console.log('下載'+file+'完成')
               self.afterFun && self.afterFun(transition)
               self.routers[transition.path].fn = SPA_RESOLVE_INIT;
               self.routers[transition.path].fn(transition)
           }
           _body.appendChild(scriptEle);
       }
    },
    //同步操作
    syncFun:function(callback,transition){
        // 路由切換后的回調
        this.afterFun && this.afterFun(transition)
        // 路由注冊時設置的回調
        callback && callback(transition)
    }
    ...

同步簡單一點我們先說同步吧,map注冊路由的第二個參數(shù)是callback,在hash發(fā)發(fā)生變化的時候refresh里面調用的回調?;旧鲜侵苯诱{用回調就行了

然后我們來看看異步的部分。異步部分懶加載js文件。
這里注意一下一開始有個判斷

    if(self.routers[transition.path].fn){
        self.afterFun && self.afterFun(transition)
        self.routers[transition.path].fn(transition)
    }else{
    ...

這個fn算是一個標示位,異步加載的js文件里面會設置一個叫SPA_RESOLVE_INIT的變量。

   SPA_RESOLVE_INIT = null;
   scriptEle.onload= function(){
       console.log('下載'+file+'完成')
       self.afterFun && self.afterFun(transition)
       self.routers[transition.path].fn = SPA_RESOLVE_INIT;
       self.routers[transition.path].fn(transition)
   }

而對應的js文件內容可能是

SPA_RESOLVE_INIT = function(transition) { 
    document.getElementById("content").innerHTML = '<p style="color:red;">當前異步渲染詳情頁'+ JSON.stringify(transition) +'</p>'
    console.log("首頁回調" + JSON.stringify(transition))
}

所以可以通過fn判斷當前路由js文件已經下載下來了就不需要再次下載了,直接調用。

后記

本文大致解析了一下簡單SPA的路由部分,所解析代碼也是比較簡單的,也有很多可以改進的地方,讀者有什么好的改進方案可以在評論區(qū)溝通交流一下。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,365評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,711評論 19 139
  • 相信很多人都聽過那個故事。王子向公主求婚,公主要求王子在城堡外為她守衛(wèi)100天,王子只要守足一百天,就可以娶到公主...
    katykat閱讀 2,262評論 0 0
  • (本文是筆者對《佐藤可士和的超整理術》和《斷舍離》兩本書的讀后筆記。) 提綱 超整理術是什么? 斷舍離是什么? 相...
    安靜的貓咪先生閱讀 1,057評論 4 11
  • 有很多因素可以決定我們成為怎樣的人,但在這里,具體談談五個關鍵的因素。 第一點,遺傳因素。我們都知道,環(huán)境對于一個...
    半夏_1854閱讀 794評論 0 0

友情鏈接更多精彩內容