前言
本文假設讀者使用過或者了解什么是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ū)溝通交流一下。