koa框架會用也會寫—(koa-router)
- koa-session:讓無狀態的http擁有狀態,基於cookie實現的後臺儲存資訊的session
- koa-mysql:封裝了需要用到的SQL語句
- koa-mysql-session:當不想讓session儲存到記憶體,而想讓session儲存到mysql資料庫中時使用
- koa-router:後臺會接受到各種請求的url,路由會根據不同的url來使用不同的處理邏輯。
- koa-view:請求html頁面時,後臺會用模板引擎渲染資料到模板上,然後返回給後臺
- koa-static:請求img、js、css等檔案時,不需要其他邏輯,只需要讀取檔案
- koa-better-body:post上傳檔案時,解析請求體
koa系列文章:
- ofollow,noindex">koa框架會用也會寫—(koa的實現)
- koa框架會用也會寫—(koa-router)
- koa框架會用也會寫—(koa-view、koa-static)
- koa框架會用也會寫—(koa-bodyparser、koa-better-body)
koa-router的使用
var Koa = require('koa'); var Router = require('koa-router'); var app = new Koa(); var router = new Router(); router.get('/home',(ctx,next)=>{ ctx.body = 'home' next(); }); router.get('/user', (ctx, next) => { ctx.body = 'user'; next(); }); app.use(router.routes()).use(router.allowedMethods()); 複製程式碼
koa-router的奧祕
假如沒有koa-router
var Koa = require('koa'); var Router = require('koa-router'); var app = new Koa(); var router = new Router(); //將路由的處理交給中介軟體 app.use((ctx, next) => { if (ctx.path === '/' && ctx.method === 'GET') { ctx.body = '首頁' } else { next(); } }) app.use((ctx, next) => { if (ctx.path === '/user' && ctx.method === 'GET') { ctx.body = '使用者' } else { next(); } }); 複製程式碼
從上面可以知道,如果沒有koa-router,其實每個路由使用的koa註冊中介軟體的形式來進行處理的,這樣不利於鬆耦合和模組化,所以將所有路由的處理邏輯抽離出來組合成一個大的中介軟體koa-router來處理,最後將大的中介軟體註冊到koa上,如果關於koa中介軟體原理還不瞭解,可以參考另一篇文章koa框架會用也會寫—(koa的實現)
koa-router的原理
既然koa-router也是大的中介軟體,裡面擁有許多小的中介軟體,那麼裡面必然也需要用到洋蔥模型,洋蔥模型的特點:
- middles:存放中介軟體的容器,用來存放註冊的中介軟體
- get(path,fn):用來註冊中介軟體,往middles中存放,由於是路由中介軟體這裡多了一個引數path
- compose():用來組合中介軟體,讓路由中介軟體按順序執行
- routes():用來將koa-router中介軟體註冊到app的中介軟體上,主要作用是呼叫路由中介軟體匹配請求的路徑ctx.path
如果對於中介軟體和洋蔥模型有疑問的,可以參考koa框架會用也會寫—(koa的實現)
middles:存放中介軟體的容器,用來存放註冊的中介軟體
class Router { constructor(){ this.middles=[]; } } module.exports = Router 複製程式碼
get(path,fn):用來註冊中介軟體,往middles中存放,由於是路由中介軟體這裡多了一個引數path
class Router { constructor(){ this.middles=[]; } get(path,fn){//set,post等同理 let layer = { path, fn, method } //處理類似/article/:id的路由 if(path.includes(':')){ let params= []; let reg = path.replace(/:([^\/]*)/g,function () { params.push(arguments[1]);//params = [id] return '([^\/]*)'//返會字串/article/([^\/]*) }); //將返回的字串變成正則,後面解析路由是會用到 layer.reg = new RegExp(reg);//返回/\/article\/([^\/]*)/ layer.params = params; } this.middles.push(layer); } } module.exports = Router 複製程式碼
compose():用來組合中介軟體,讓路由中介軟體按順序執行
class Router { constructor(){ this.middles=[]; } get(path,fn){//set,post等同理 let layer = { path, fn, method } //處理類似/article/:id的路由 if(path.includes(':')){ let params= []; let reg = path.replace(/:([^\/]*)/g,function () { params.push(arguments[1]);//params = [id] return '([^\/]*)'//返會字串/article/([^\/]*) }); //將返回的字串變成正則,後面解析路由是會用到 layer.reg = new RegExp(reg);//返回/\/article\/([^\/]*)/ layer.params = params; } this.middles.push(layer); } compose(lasts,next,ctx){//lasts為匹配的路由集合 dispatch(index){ if(index === lasts.length) return next(); let route = lasts[index]; //將路徑引數都取出來exp:id的值 let params = route.params; let [, ...args] = pathname.match(route.reg); ctx.request.params = params.reduce((memo,key,index)=>(memo[key] = args[index], memo), {}); //執行路由邏輯,next賦值為下一個路由邏輯 route.fn(ctx,()=>{ dispatch(index+1); }) } dispatch(0) } } module.exports = Router 複製程式碼
routes():用來將koa-router中介軟體註冊到app的中介軟體上,主要作用是呼叫路由中介軟體匹配請求的路徑ctx.path
class Router { constructor(){ this.middles=[]; } get(path,fn){//set,post等同理 let layer = { path, fn, method } //處理類似/article/:id的路由 if(path.includes(':')){ let params= []; let reg = path.replace(/:([^\/]*)/g,function () { params.push(arguments[1]);//params = [id] return '([^\/]*)'//返會字串/article/([^\/]*) }); //將返回的字串變成正則,後面解析路由是會用到 layer.reg = new RegExp(reg);//返回/\/article\/([^\/]*)/ layer.params = params; } this.middles.push(layer); } compose(lasts,next,ctx){//lasts為匹配的路由集合 dispatch(index){ if(index === lasts.length) return next(); let route = lasts[index]; route.fn(ctx,()=>{ dispatch(index+1); }) } dispatch(0) } routes() { // ctx上下文next指的是koa中的next方法 return async (ctx, next) => { let pathname = ctx.path;//請求的路徑 let lasts = this.middles.filter(item => { if (route.reg) {// 說明當前路由是一個路徑引數 if (method === route.method && route.reg.test(pathname)) { return true //路徑引數匹配到了,新增進路由組 } } if ((method === route.method || route.method === 'all') && (route.p === pathname || route.p === '*')) { return true //路徑是'/'或者'all',新增進路由組 } return false; }); this.compose(lasts, next, ctx); } } } module.exports = Router 複製程式碼