這份Koa的簡易Router手敲指南請收下

cherryvenus發表於2019-03-01

上一期連結——也就是本文的基礎,參考KOA,5步手寫一款粗糙的web框架

本文參考倉庫:點我

Router其實就是路徑匹配,通過匹配路徑,返回給使用者相應的網站內容。

以下方例子為例,主要通過提取req中的path資訊,來匹配當前路徑,並給ctx.body賦值,返回相應的介面。這個過程不復雜,就是一個匹配路徑的過程。但是這種會不會太臃腫了呢,而且很有可能路徑一多,就要被if...else...給弄暈了。

app.use((ctx,next)=>{
   //簡易路由
   let {path}=ctx
   if(path==="/"){
       ctx.body="index"
   }else if(path==="/admin"){
        ctx.body="admin"
   }else if(path==="/user"){
        ctx.body="user"
   }
})
複製程式碼

這個時候專門處理路徑的外掛就出現了,寫一個Router,專門用來管理路徑。

Router的功能一共是兩個:

  • 匹配路徑
  • 返回相應頁面

如果Router要掛載到app上,那麼語法是這樣的app.use(router.routes()),也就是說:

  • Router本身就是個中介軟體
  • 為了返回匹配的路由,寫一箇中介軟體掛到app

瞭解了Router的大概,我們開始一步步動手寫Router吧!

STEP1 建立Router

先把Router的框架寫好,一個構造器,一個get方法用於配置路由,一個routers變成路由匹配的中介軟體掛在到app上。

class Router{
    constructor(){}
    get(path,callback){}
    routers(){}
}
複製程式碼

我們獲取路由的時候,一定會配置頁面,那麼這個頁面的類也要加上了,每次get的時候,就加入一個頁面到陣列中。

class Page{
    constructor(path,callback){
        this.path=path
        this.callback=callback
    }
}
class Router{
    constructor(){
        this.pages=[]
    }
    get(path,callback){
        this.pages.push(new Page(path,callback))
    }
    routers(){}
}
複製程式碼

因為路由是對中介軟體的封裝,所以用法上是和app.use類似的:

router.get(path,(ctx,next){
    ctx.body=`xxx`
    next()
})
複製程式碼

是不是很眼熟?這個get中的callback引數就是中介軟體。

STEP2 寫一箇中介軟體,返回匹配路由的中介軟體

routers就幹三件事:

  • 篩選出匹配的路由,array.filter就可以做到
  • 組合執行這些路由
  • 返回一箇中介軟體
compose(ctx,next,routers){
    function dispatch(index){
        if(index===routers.length) return next();
        let router=routers[index]
        router(ctx,()=>dispatch(index+1));
    }
    dispatch(0)
}
routers(){
    let dispatch = (ctx,next)=>{
        let path=ctx.path    
        let routers=this.pages.filter(p=>{console.log(p.path);return p.path===path}).map(p=>p.callback)
        this.compose(ctx,next,routers)
    }
    return dispatch
}
複製程式碼

大家有沒有很眼熟,和koa中的application.js的回撥很像。其實就是一個回撥的過程,封裝之後,便於我們使用。

STEP3 給路由分個組吧

我們再寫路由的時候,如果全部寫全路徑,感覺會很囉嗦:

router.get("/admin",(ctx,next)=>{})
router.get("/admin/login",(ctx,next)=>{})
router.get("/admin/register",(ctx,next)=>{})
...
router.get("/user",(ctx,next)=>{})
router.get("/user/login",(ctx,next)=>{})
router.get("/user/register",(ctx,next)=>{})
....
複製程式碼

我們給路由分組,其實思路很簡單,就是給每個小路由新建一個Router,然後大路由用use方法,將這些路由集合到一起。

let admin=new Router()
admin.get("/",(ctx,next)=>{
    ctx.body="admin"
    next()
})
let user=new Router()
user.get("/",(ctx,next)=>{
    ctx.body="user"
    next()
})
//鏈式呼叫~
let router=new Router()
router.use("/admin",admin.routers())
.use("/user",user.routers())

app.use(router.routers())
複製程式碼

那麼問題來了,use要怎麼寫呢才能組合這些routers??我們先來分析下use的功能:

  • 組合路徑
  • 將route加入當前物件的陣列中

use中有兩個引數一個path,一個router.routers()的中介軟體,可是我們需要router陣列物件,所以我們可以這麼做:

routers(){
    let dispatch = (ctx,next)=>{
      .....
    }
    dispatch.router=this
    return dispatch
}
複製程式碼

在中介軟體上暗搓搓地加一個router的物件,將自己一起傳遞出去,有麼有很機智

有了router的陣列物件,那麼use這個方法就很好實現了,將page迴圈一波,加入當前物件的pages,就好了。這裡再將自己返回,然後就可以愉快地使用鏈式呼叫了。

 use(path,middleware) {
    let router = this;
    middleware.router.pages.forEach(p => {
        router.get(path+p.path,p.callback)
    });
    return router
}
複製程式碼

step4 LAST BUT NOT LEAST

大家需要注意,還記得上一期講的async/await非同步嗎?

如果有任何除了路由的操作都要放在路由上方執行,因為路由只是匹配路徑,返回結果,並沒有async/await操作。

所以一定注意:

這樣是有效的·,頁面返回aaa

app.use(async (ctx,next)=>{
    await makeAPromise(ctx).then(()=>{next()})
})
...
app.use(router.routers())
複製程式碼

這樣是無效的,頁面不會返回aaa

...
app.use(router.routers())
app.use(async(ctx,next)=>{
    await next()//等待下方完成後再繼續執行
    ctx.body="aaa"
})
複製程式碼

相關文章