koa以及express

githtml

express

  • 功能全,內置了不少的中間件,集成了路由,靜態服務,模版引擎等功能,基於es5,所有基於回調實現
  • 錯誤只能在回調內部處理
  • app.all, app.get,app.post,app.use
  • 在原生的req,res中拓展屬性
  • 和express同樣基於http模塊

express使用

const express = require('express') // 導出的是一個函數
const app = express()
app.use('/', (req, res, next) => {
    //  在中間件中處理公共路徑
    next()
})
// 內部不會將回調函數包裝成promise,內部集成了路由
app.get('/', (req, res)=> {
    res.end('ok')
})
app.listen(3000, () => {
    console.log('start')
})
複製代碼

源碼解析

路由系統
// 主要代碼
// 'application.js'
const http = require('http')
const Router = require('./router')
/*
  @ 在初始化application的時候,須要建立一個router實例,中間的請求處理都讓router去完成
@*/

function Application() {
  // 在建立應用的時候,初始化一個router
  this._router = new Router()
}
Application.prototype.get = function (path, ...handlers) {
  this._router.get(path, handlers) // 
},
Application.prototype.listen = function (...arg) {
  const server = http.createServer((req, res) => {
    function done() {
      // 若是router處理不了,直接執行done
      res.end('not xx `Found')
    }
    this._router.handle(req, res, done)
  })
  server.listen(...arg)
}

module.exports = Application

// 'layer.js'
function Layer(path, handle) {
  this.path = path
  this.handle = handle
}
module.exports = Layer

// 'route.js'
const Layer = require("./layer");

function Route() {
  // route也有一個stack,存放內層的layer(方法類型與用戶傳入函數的對應)
  this.stack = []
}
/*
@ route有一個diapatch屬性,調用一次執行內層的stack
@ 還有一個get方法,用來標記內層layer函數與方法類型的對應關係
*/
Route.prototype.dispatch = function(req, res, out) {
  let idx = 0
  const next = () => {
    
    if (idx >= this.stack.length) return out()
    const routeLayer = this.stack[idx++]
    // 比較方法
    if (routeLayer.method ===  req.method.toLowerCase()) {
      routeLayer.handle(req, res, next)
    } else {
      next()
    }
  }
  next()
}
Route.prototype.get = function(handles) {
  handles.forEach(handle => {
    const layer = new Layer('', handle)
    
    layer.method = 'get'
    this.stack.push(layer)
  });
}
module.exports = Route



// 'index.js'
/* 
  @router有route屬性和layer屬性,layer存路徑和route.dispatch的對應關係,route存放用戶真實的回調
  @ router是真個路由系統,route是一條條的路由
  @ 在express的路由系統中,又一個外層的棧,存放着路徑與回調函數的對應關係,這個回調函數是route的diapatch,
  @ 當調用外層的handle時,會讓route.dispatch執行,dispatch找到route中存放的layer,根據方法進行匹配,一次執行
  @ 內層的layer存放的是方法類型與真實回調的對應關係
*/
const Layer = require('./layer')
const Route = require('./route')
const url = require('url')
function Router() {
  this.stack = []
}
Router.prototype.route = function (path) {
  const route = new Route() // 建立route
  const layer = new Layer(path, route.dispatch.bind(route)) // 建立外層layer
  layer.route = route // 爲layer設置route屬性
  // 將layer存放到路由系統的stack中
  this.stack.push(layer)
  return route
}
// router中有get方法與handle放法
Router.prototype.get = function (path, handlers) {
  // 當調用get時咱們建立一個layer,給他添加對應關係
  // 建立對應關係
  const route = this.route(path)
  // 讓route調用get方法標記route中的每一個layer是何種方法
  route.get(handlers)
}
Router.prototype.handle = function (req, res, done) {
  /*
    @ 當有請求過來時,咱們先拿到path找到外層的layer,而後執行他的handle,讓內層的route,根據method依次執行內層的layer的handle
    @
  */
  let { pathname } = url.parse(req.url)
  let idx = 0
  let next = () => {
    if (idx >= this.stack.length) return done() // 遍歷完後仍是沒找到,那就直接走出路由系統便可
    let layer = this.stack[idx++]

    // 須要查看 layer上的path 和 當前請求的路徑是否一致,若是一致調用dispatch方法
    if (layer.path === pathname) {
      // 路徑匹配到了 須要讓layer上對應的dispatch執行
      layer.handle(req, res, next) // 將遍歷路由系統中下一層的方法傳入
    } else {
      next()
    }
  }
  next()
}
module.exports = Router

複製代碼
  • express導出的是一個函數,在執行這個函數的時候咱們建立一個應用,在建立應用的同時建立一個路由系統,讓路由系統處理接下來的請求
  • 針對實例化出來的路由系統router,存着一層一層layer,如下統稱爲外層的layer,在外層的每一個layer有個route屬性,這個屬性中存着一個一個的內層layer,在內層的layer中存放着方法名和用戶傳來的真實的回調的對應關係,一個layer存着一個回調,外層layer存放着路徑與route.dispatch的對應關係,當匹配到外城laryer路徑時,觸發route.dispatch執行,而後讓route中存放的內層layer依次執行
路由的懶加載策略
function Application() {
  // 在建立應用的時候,初始化一個router
}
Application.prototype.lazy_router = function() {
  if (!this._router) {
    this._router = new Router() // 在調用了對應的方法或者listen的建立路由
  }
}
methods.forEach(method => {
  Application.prototype[method] = function (path, ...handlers) {
    this.lazy_router()
    ...
  }
})
Application.prototype.listen = function (...arg) {
  const server = http.createServer((req, res) => {
    this.lazy_router() // 實現路由的懶加載
    ...
}

module.exports = Application
複製代碼
  • 不在一建立應用就生成一個路由系統,而是用戶獨傲了express對應的方法或者listen的時候採起建立路由系統
其餘部分的優化

咱們在拿到一個請求的路徑和方法後,先去外層layer匹配path,path匹配以後,在去內層匹配methods,這就存在內層匹配不到對應的方法,形成浪費,所以咱們在外層layer的route屬性中加一個methods屬性,這個屬性包括route中全部存在的方法,在當匹配到外層,先去methods映射去找是否存在,若是存在就觸發route.dispatch,不存在則直接跳過,匹配下一個layergit

// index.js
Router.prototype.handle = function (req, res, done) {
    ...
  let next = () => {
    ...

    // 須要查看 layer上的path 和 當前請求的路徑是否一致,若是一致調用dispatch方法
    if (layer.match(pathname)) {
      // 路徑匹配到了 須要讓layer上對應的dispatch執行
      if (layer.route.methods[req.method.toLowerCase()]) {
        layer.handle(req, res, next) // 將遍歷路由系統中下一層的方法傳入
      } else {
        next()
      }
    } else {
      next()
    }
  }
  next()
}
module.exports = Router

// route.js

function Route() {
    ...
  this.methods = {}
}
...無用的代碼省略
methods.forEach(method => {
  Route.prototype[method] = function(handles) {
    handles.forEach(handle => {
      const layer = new Layer('', handle)
      this.methods[method] = true
        ...
    });
  }

})

module.exports = Route

複製代碼
中間件
洋蔥模型
...
app.get('/', function(req, res, next) {
    console.log(1)
    setTimeout(() => {
        next();
        console.log('xxx')
    }, 6000);
}, function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next();
    console.log(111);

})
app.get('/', function(req, res, next) {
    console.log('2');
    res.end('end')
})
app.post('/', function(req, res, next) {
  res.end('post ok')
})
app.listen(3001, () => {
    console.log('server start 3000');
})
// 1, 2, 111, 11,xxx
解析: 洋蔥模型,其實就是從外面一層層的深刻,再一層層的穿出來,中間件的執行順序嚴格遵循洋蔥模型,next能夠理解爲先執行下一個中間件,next以後的代碼會在執行完下一個中間件再回來執行
上述代碼的輸出: 
    + 1輸出毫無疑問,
    + next在setTimeOut中等待,到時間後先進入下一個中間件,在下一個中間件中next在輸出之前,繼續先執行下一個回調,輸出2
    + 再回到上一層,輸出111
    + 回到上一層輸出11
    + 回到最開始的那一層,輸出xxx
複製代碼
use的實現
中間件能夠用來作什麼
  • 中間件能夠決定是否向下執行,攔截請求進行中間操做
  • 權限校驗
  • 擴展屬性
用法
// + 第一個參數,匹配以path開頭的路徑 ,第二個參數是回調
+ 中間件不匹配方法,只匹配路徑
app.use('/', (req, res, next) => {
  // todo
})
複製代碼
實現
  • 中間件和路由放在同一個路由系統中,他的layer只有path(以path開頭)以及用戶傳遞的回調函數,與路由不一樣的是中間件沒有route屬性
  • 中間件須要在內部調用next纔會調用下一步
  • 中間件的位置在路由系統中出於路由的前方
// application.js
Application.prototype.use = function() {
  // 中間件的實現邏輯,只要用到路由就要判斷一次路由是否加載
  this.lazy_router()
  // 將邏輯交給路由系統中實現
  this._router.use(...arguments)
}

// index.js
...
Router.prototype.use = function(path, ...handles) {
  if(typeof path == 'function') {
    // 只傳遞了一種參數
    handles.unshift(path)
    path = '/'
  }

  // 遍歷handles建立layer
  handles.forEach(handle => {
    let layer = new Layer(path, handle)
    // 對於中間件,沒有route屬性,爲了區分,設置route屬性爲undeinfed,而且把這個layer放入到路由系統中
    layer.route = undefined
    this.stack.push(layer)
  })
}


Router.prototype.handle = function (req, res, done) {
  let { pathname } = url.parse(req.url)
  let idx = 0
  let next = () => {
    ...
    if (layer.match(pathname)) {
      // 當匹配到路徑的後,可能爲中間件,可能爲路由
      if (!layer.route) { // 判斷是不是中間件
        // 中間件的話,直接去執行
        layer.handle_request(req, res, next)
      } else {
        // 路由
        if (layer.route.methods[req.method.toLowerCase()]) {
          layer.handle(req, res, next) // 將遍歷路由系統中下一層的方法傳入
        } else {
          next()
        }

      }
      // 路徑匹配到了 須要讓layer上對應的dispatch執行
    } else {
      next()
    }
  }
  next()
}

// layer.js

function Layer(path, handle) {
  this.path = path
  this.handle = handle
}
Layer.prototype.match = function (pathname) {
  // 路由和中間件的匹配規則不一樣
  if (this.path === pathname) return true
  if (!this.route) {
    // 中間件
    if (this.path === '/') return true
    return pathname.startsWith(this.path + '/')
  }
  return false

}
Layer.prototype.handle_request = function(req, res, next) {
  this.handle(req, res, next)

}
module.exports = Layer
複製代碼

express的錯誤處理

  • 與koa監聽error時間相比,express採起的是使用中間件處理錯誤
  • 錯誤處理中間件的特色是,參數有四個(err, req, res, next)
用法
const express = require('express');

const app = express();
app.use('/', (req, res, next) => {
  // todo
  console.log(1)
  next('出錯了')
})
app.use('/', (req, res, next) => {
  // todo
  console.log(2)
  next()
})
app.get('/', function(req, res, next) {
    console.log(1)
    setTimeout(() => {
        next();
        console.log('xxx')
    }, 10000);
}, function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next();
    console.log(111);

})
複製代碼

  • express基於異步回調,所以不可使用tryCatch捕獲異常,在使用expres中,若是在next中傳遞參數,就認爲是出錯了,不會繼續往下執行
實現
// route.js
...
Route.prototype.dispatch = function(req, res, out) {
  let idx = 0
  const next = (err) => {
    if(err) { return out(err)} // out就是外層的next,若是內層有錯誤,直接跳出
    
    if (idx >= this.stack.length) return out()
    const routeLayer = this.stack[idx++]
    // 比較方法
    if (routeLayer.method ===  req.method.toLowerCase()) {
      routeLayer.handle(req, res, next)
    } else {
      next()
    }
  }
  next()
}
...


// index.js
Router.prototype.handle = function (req, res, done) {
  /*
    @ 當有請求過來時,咱們先拿到path找到外層的layer,而後執行他的handle,讓內層的route,根據method依次執行內層的layer的handle
    @
  */
  let { pathname } = url.parse(req.url)
  let idx = 0
  let next = (err) => {
    // 在這裏進行統一的監聽
    if (idx >= this.stack.length) return done() // 遍歷完後仍是沒找到,那就直接走出路由系統便可
    let layer = this.stack[idx++]
    if (err) { //錯誤處理。通常放在最後
      // 進入到錯誤能夠是中間件也能夠是路由
      if (!layer.route) {
        // 找中間件
        layer.handle_error(err, req, res, next)
      } else {
        // 路由,繼續攜帶錯誤信息匹配中間件
        next(err)
      }

    } else {
      if (layer.match(pathname)) {
        // 當匹配到路徑的後,可能爲中間件,可能爲路由
        if (!layer.route) {
          // 中間件的化,直接去執行
          // 排除錯誤中間件
          if (layer.handle.length !== 4) {
            layer.handle_request(req, res, next) // 普通中間件
          } else {
            // 是錯誤中間件,跳出
            next()
          }
        } else {
        ...
 
        }
      } else {
        next()
      }
    }
  }
  next()
}

// layer.js
...
Layer.prototype.handle_error = function(err, req, res, next) {
  // 找到錯誤處理中間件所在的layer,若是不是帶着錯誤信息繼續next
  if(this.handle.length === 4) {
    // 錯誤處理中間件,讓handle執行
    return this.handle(err, req, res, next)
  }
  next(err) // 普通的中間件
}
...


測試代碼
server.js

app.use('/', (req, res, next) => {
  // todo
  console.log(1)
  next()
})

app.use('/', (req, res, next) => {
  // todo
  console.log(2)
  next()
})
app.use('/', (req, res, next) => {
  // todo
  console.log(3)
  next()
})
// 路由的中間件  將處理邏輯 拆分紅一個個的模塊
app.get('/', function(req, res, next) {
    console.log(1)
    next()
}, function(req, res, next) {
    next();
    console.log(11)
}, function(req, res, next) {
    next('出錯了');
    console.log('出錯了');

})
app.get('/', function(req, res, next) {
    console.log('2');
    res.end('end')
})
app.use((err,req,res,next)=>{ 
  next();
})
複製代碼

  • 在中間件的next是外層layer往下執行的next,而路由是內層layer的next

二級路由的實現

使用
// server.js
const express = require('express');
const LoginRouter =require('./routes/loginRouter');

const app = express();

app.use('/login',LoginRouter);

app.listen(3000);

// loginRouter.js
const express = require('express');
let router = express.Router(); // 是個構造函數
router.get('/add',function (req,res) {
    res.end('/login-add')
})
router.get('/out',function (req,res) {
    res.end('/login-out')
})

module.exports = router;
複製代碼
實現
  • 這裏的express.Router就是index中定義的Router
// router/index.js
...
// 提供一個Router類,既能夠new也能夠執行
createApplication.Router = require('./router')
...

複製代碼
Router兼容
  • 使得Router既能夠new,也能夠執行
  • new的時候會返回一個引用類型,this指向這個引用類型
function Router() {
  this.stack = []
  const router = (req, res, next) => {
    router.handle(req, res, next)
  }
  router.stack = []
  router.__proto__ = proto
  return router
}
複製代碼

這樣改造後,經過new以後,this均指向router這個函數,在這個函數上沒有以前的各類放法,所以以前寫的邏輯均失效, 須要把這些屬性放到原型鏈上es6

二級路由
  • 當咱們調用express.Router()是會建立一個獨立的路由系統,和之前的路由系統結構類型類似,這額路由系統和路由的第一層相對應
  • 當請求到來時,先匹配第一級路由,匹配後進入二級路由系統,匹配二級路由,匹配到,調用dispatch,匹配不到回到一級路由系統中,匹配下一個
  • 當使用中間件的時候,匹配到一級路由時,把一級路由路徑剪切掉,取剩下的路徑進入二級路由系統中進行匹配,出來的時候再拼上路由
// index.js


proto.handle = function (req, res, done) {
  /*
    @ 當有請求過來時,咱們先拿到path找到外層的layer,而後執行他的handle,讓內層的route,根據method依次執行內層的layer的handle
    @
  */
  let { pathname } = url.parse(req.url)
  let idx = 0
  let removed = '';
  let next = (err) => {
    // 在這裏進行統一的監聽
    if (idx >= this.stack.length) return done() // 遍歷完後仍是沒找到,那就直接走出路由系統便可
    let layer = this.stack[idx++]
    // 出來中間件的時候將路徑補氣
    if (removed) {
      req.url = removed + pathname;// 增長路徑 方便出來時匹配其餘的中間件
      removed = '';
    }
    if (err) { //錯誤處理。通常放在最後
        ...
    } else {
      if (layer.match(pathname)) {
        if (!layer.route) {
          if (layer.handle.length !== 4) {
            if (layer.path !== '/') {
              // 進入中間件的時候剪切路徑
              removed = layer.path // 中間件的路徑
              req.url = pathname.slice(removed.length);
            }
            layer.handle_request(req, res, next) // 普通中間件
          } else {
            // 是錯誤中間件,跳出
            next()
          }
        } else {
        ...
      } else {
        next()
      }
    }
  }
  next()
}
複製代碼

koa

  • 小,基於es6,(promise,async,await),主要關注核心use方法
  • 中間件
  • 洋蔥模型
  • 源碼思路
    • 構造函數添加了request,reponse,context
    • use的時候將middleware添加到數組中
    • listen的時候,調handleRequest
    • 在handleRequest中,根據req,res建立一個上下文,讓中間件一次執行後(返回一個promise),將結果放在ctx.body上
  • 中間件
  • 錯誤處理,監聽onerror
  • ctx有req,res,request,reponse

實現

application
  • 建立koa應用,導出一個類,類上有use,listen等方法
  • 在listen方法中使用http建立一個server,處理請求
    • 根據請求的req,res建立一個上下文
    • 而後一個一個的執行註冊的中間件,中間件返回的是一個一個的promise,在中間件執行完成後,將放置在ctx.body返回給用戶
  • use中放置的是一個一個的中間件
// applicatin
const EventEmitter = require('events')
const http = require('http')
const context = require('./context.js')
const request = require('./request.js')
const response = require('./response.js')
const Stream = require('stream')

class Application extends EventEmitter {
  constructor() {
    super()
    // 爲了實現每new一次有個全新的額context,所以須要使用object.create()賦值一份
    this.context = Object.create(context)
    this.response = Object.create(response)
    this.request = Object.create(request)
    this.middlewares = []
  }
  
  use(middleware) {
    this.middlewares.push(middleware)
  }
  generateContext(req, res) {
    ....
  }
  compose(ctx) {
    ....
  }
  handleRequest(req, res) {
    // 根據req,res以及新的context生成一個ctx
    const ctx = this.generateContext(req, res)
    // 執行中間件
    this.compose(ctx).then(() => {
      // 對返回的處理
      let body = ctx.body; //當組合後的promise完成後,拿到最終的結果 響應回去

      if(typeof body == 'string' || Buffer.isBuffer(body)){
          res.end(body);
      }else if(body instanceof Stream){
          res.setHeader('Content-Disposition',`attachement;filename=${encodeURIComponent('下載1111')}`)
          body.pipe(res);
      }else if(typeof body == 'object'){
          res.end(JSON.stringify(body));
      }
    })
  }
  listen(...args) {
    const server = http.createServer(this.handleRequest.bind(this))
    server.listen(...args)
  }
}
module.exports = Application
複製代碼
generateContext,根據req,res建立上下文
  • generateContext會在context中拓展一個request和reponse,在response以及request上有原生的res以及req
  • 複製兩次: 第一次是每次new Koa()時保證每個實例拿到都是新的context,request,response + 第二次複製是爲了每次use都拿到一個新的context,request,response
generateContext(req, res)
    // 保證每次use都建立新的上下文
    let context = Object.create(this.context);
    let request = Object.create(this.request);
    let response = Object.create(this.response);
    // 上下文中有一個request對象 是本身封裝的的對象
    context.request = request;
    context.response = response;
    // 上下文中還有一個 req屬性 指代的是原生的req
    // 本身封裝的request對象上有req屬性
    context.request.req = context.req = req;
    context.response.res = context.res = res;
    return context;
}
複製代碼
compose(中間件)
異步回調使用next類型的
compose(ctx) {
    const dispatch = i => {
        try {
            if(i >= this.middlewares.length) return Promise.reslove()
            const currentMiddle = this.middlewares[i]
            return Promise.reslove(currentMiddle(ctx, () => dispatch(i+1))
            
        } catch(e) {
            
        }
    }
    dispatch(0)
}
複製代碼

context

  • 在對象初始化時定義 在對象定義後經過Object的__defineGetter__、__defineSetter__方法來追加定義
  • 訪問ctx.query,去找ctx.requesrt.query,至關於作一層代理
  • ctx.body去找 ctx.response.body
  • 設置ctx.body,至關於設置ctx.response.body
const context = {
}
// 代理方法
function defineGetter(target, key) {
  // 定義一個getter, context和this不是一個,this.__proto__.__proto__ = context
  context.__defineGetter__(key, () => {
    return this[target][key]
  })
}

function defineSetter(target, key) {
  context.__defineSetter__(key, (newValue) => {
    this[target][key] = newValue
  })
}
defineGetter('request', 'url')
defineGetter('request', 'path') 
defineGetter('request', 'query') // ctx.query = ctx.requesrt.query

defineGetter('response','body');// ctx.body => ctx.response.body
defineSetter('response','body');// ctx.body => ctx.response.body
module.exports = context
複製代碼

request

  • 使用屬性訪問器設置一層代理,
const url = require('url')
const request = {

  // 屬性訪問器,相似Object.definedProperty()
  get url() {
    // 這裏的this指的是ctx.request
    return this.req.url
  },
  get path() {
    return url.parse(this.req.url).pathname
  },
  // 但願有什麼屬性就擴展
  get query() {
    return url.parse(this.req.url, true).query
  }
}
module.exports = request
複製代碼

response

const response = {
  _body:'',
  get body(){
      return this._body;
  },
  set body(val){
      this._body = val;
  }
}
module.exports = response;
複製代碼

body-parse中間件

  • body-parse中間件其實就是在ctx.request.body上把傳入的參數解析出來,發來的請求時一個可讀流,所以監聽data或者end事件
const querystring = require('querystring');
const uuid = require('uuid');
const path = require('path');
const fs = require('fs');
// 中間件的功能能夠擴展屬性 / 方法
module.exports = function(uploadDir) {
    return async (ctx, next) => {
        await new Promise((resolve,reject)=>{
            const arr = [];
            ctx.req.on('data',function (chunk) {  
                arr.push(chunk);
            })
            ctx.req.on('end',function () {
                if(ctx.get('content-type') === 'application/x-www-form-urlencoded'){
                    let result = Buffer.concat(arr).toString();
                    ctx.request.body = querystring.parse(result);
                }
                if(ctx.get('content-type').includes('multipart/form-data')){ // 二進制不能直接toString 可能會亂碼
                    let result = Buffer.concat(arr); // buffer
                    let boundary = '--'+ ctx.get('Content-Type').split('=')[1];
                    let lines = result.split(boundary).slice(1,-1);
                    let obj = {}; // 服務器收到的結果所有放在這個對象中

                    lines.forEach(line=>{
                        let [head,body] = line.split('\r\n\r\n');
                        head = head.toString();
                        let key = head.match(/name="(.+?)"/)[1]
                        if(!head.includes('filename')){
                            obj[key] = body.toString().slice(0,-2);
                        }else{
                            // 是文件  文件上傳 名字須要是隨機的
                            let content = line.slice(head.length + 4,-2);
                            let filePath = path.join(uploadDir,uuid.v4());
                            obj[key] = {
                                filePath,
                                size:content.length
                            }
                            fs.writeFileSync(filePath,content);
                        }
                    });
                    ctx.request.body = obj;
                }
                resolve();
            })
        });
        await next(); // 完成後須要繼續向下執行
    }   
}
Buffer.prototype.split = function (sep) { // 分隔符多是中文的,我但願將他轉化成buffer來計數
    let sepLen = Buffer.from(sep).length;
    let arr = [];
    let offset = 0;
    let currentIndex = 0;
    while((currentIndex = this.indexOf(sep,offset)) !== -1){
        arr.push(this.slice(offset,currentIndex));
        offset = currentIndex + sepLen;
    }
    arr.push(this.slice(offset));
    return arr;
}
複製代碼

koa項目搭建

項目目錄 github

劃分路由
const Router = require('koa-router')
const routerA = new Router({prefix: '/login'}) // 劃分接口以loagin開頭的

複製代碼
koa-combine-routers合併路由
const combineRouters = require('koa-combine-routers')
combineRouters(A, B)// 將A和B路由合併,返回一箇中間件
複製代碼
渲染模版koa-views
const views = require('koa-views')// 在ctx加一個render方法,基於Promise
app.use(views(__dirname + './views', {
    map: {
        html: 'ejs' //使用ejs模版
    }
}))

// 使用
class Controller {
    async add(ctx, next) {
        await ctc.render('a.html', { age: 11, name: 'xx'})  
    }
}
// 模版
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <%=name%> <%=age%>
</body>
</html>

複製代碼
相關文章
相關標籤/搜索