不到300行代码构建精简的koa和koa-router(mini-koa)

《不到300行代码构建精简的koa和koa-router(mini-koa)》

媒介

鉴于之前运用expresskoa的履历,这两天想尝试构建出一个koa精简版,运用起码的代码完成koa和koa-router,同时也梳理一下Node.js收集框架开辟的中心内容。

完成后的中心代码不凌驾300行,源代码配有细致的解释。

中心设想

API挪用

mini-koa的API设想中,参考koa和koa-routerAPI挪用体式格局。

Node.js的收集框架封装实在并不庞杂,个中心点在于http/httpscreateServer要领上,这个要领是http要求的进口。

起首,我们先回忆一下用Node.js来启动一个简朴效劳。

// https://github.com/qzcmask/mini-koa/blob/master/examples/simple.js
const http = require('http')
const app = http.createServer((request, response) => {
  response.end('hello Node.js')
})
app.listen(3333, () => {
  console.log('App is listening at port 3333...')
})

路由道理

既然我们晓得Node.js的要求进口在createServer要领上,那末我们能够在这个要领中找出要求的地点,然后依据地点映射出监听函数(经由过程get/post等要领增加的路由函数)即可。

个中,路由列表的花样设想以下:

// binding的花样
{
'/': [fn1, fn2, ...],
'/user': [fn, ...],
...
}
// fn/fn1/fn2的花样
{
  method: 'get/post/use/all',
  fn: '路由处置惩罚函数'
}

难点剖析

next()要领设想

我们晓得在koa中是能够增加多个url监听函数的,个中决议是不是通报到下一个监听函数的症结在于是不是挪用了next()函数。假如挪用了next()函数则先把路由权转移到下一个监听函数中,处置惩罚完毕再返回当前路由函数。

mini-koa中,我把next()要领设想成了一个返回Promise fullfilled的函数(这里简朴设想,不斟酌next()传参的状况),用户假如挪用了该函数,那末就能够依据它的值来决议是不是转移路由函数处置惩罚权。

推断是不是转移路由函数处置惩罚权的代码以下:

let isNext = false
const next = () => {
  isNext = true
  return Promise.resolve()
}
await router.fn(ctx, next)
if (isNext) {
  continue
} else {
  // 没有挪用next,直接中断要求处置惩罚函数
  return
}

use()要领设想

mini-koa供应use要领,可供扩大日记纪录/session/cookie处置惩罚等功能。

use要领实行的道理是依据要求地点在实行特定路由函数之前先实行mini-koa挪用use监听的函数

所以这里的症结点在于怎样找出use监听的函数列表,假定现有监听状况以下:

app.use('/', fn1)
app.use('/user', fn2)

假如接见的url/user/add,那末fn1和fn2都必须要顺次实行。

我采用的做法是先依据/字符来支解要求url,然后轮回拼接,检察路由绑定列表(binding)中有没有要use的函数,假如发现有,增加进要use的函数列表中,没有则继承下一次轮回。

细致代码以下:

// 默许use函数前缀
let prefix = '/'
// 要预先挪用的use函数列表
let useFnList = []

// 支解url,运用use函数
// 比方item为/user/a/b映射成[('user', 'a', 'b')]
const filterUrl = url.split('/').filter(item => item !== '')
// 该reduce的作用是找出本要求要use的函数列表
filterUrl.reduce((cal, item) => {
  prefix = cal
  if (this.binding[prefix] && this.binding[prefix].length) {
    const filters = this.binding[prefix].filter(router => {
      return router.method === 'use'
    })
    useFnList.push(...filters)
  }
  return (
    '/' +
    [cal, item]
      .join('/')
      .split('/')
      .filter(item => item !== '')
      .join('/')
  )
}, prefix)

ctx.body相应

经由过程ctx.body = '相应内容'的体式格局能够相应http要求。它的完成道理是运用了ES6Object.defineProperty函数,经由过程设置它的setter/getter函数来到达数据追踪的目标。

细致代码以下:

// 追踪ctx.body赋值
Object.defineProperty(ctx, 'body', {
  set(val) {
    // set()内里的this是ctx
    response.end(val)
  },
  get() {
    throw new Error(`ctx.body can't read, only support assign value.`)
  }
})

子路由mini-koa-router设想

子路由mini-koa-router设想这个比较简朴,每一个子路由保护一个路由监听列表,然后经由过程挪用mini-koaaddRoutes函数增加到主路由列表上。

mini-koaaddRoutes完成以下:

addRoutes(router) {
  if (!this.binding[router.prefix]) {
    this.binding[router.prefix] = []
  }
  // 路由拷贝
  Object.keys(router.binding).forEach(url => {
    if (!this.binding[url]) {
      this.binding[url] = []
    }
    this.binding[url].push(...router.binding[url])
  })
}

用法

运用示例以下,源代码能够在github上找到:

// examples/server.js
// const { Koa, KoaRouter } = require('mini-koa')
const { Koa, KoaRouter } = require('../index')
const app = new Koa()
// 路由用法
const userRouter = new KoaRouter({
  prefix: '/user'
})

// 中间件函数
app.use(async (ctx, next) => {
  console.log(`要求url, 要求method: `, ctx.req.url, ctx.req.method)
  await next()
})

// 要领示例
app.get('/get', async ctx => {
  ctx.body = 'hello ,app get'
})

app.post('/post', async ctx => {
  ctx.body = 'hello ,app post'
})

app.all('/all', async ctx => {
  ctx.body = 'hello ,/all 支撑一切要领'
})

// 子路由运用示例
userRouter.post('/login', async ctx => {
  ctx.body = 'user login success'
})

userRouter.get('/logout', async ctx => {
  ctx.body = 'user logout success'
})

userRouter.get('/:id', async ctx => {
  ctx.body = '用户id: ' + ctx.params.id
})

// 增加路由
app.addRoutes(userRouter)

// 监听端口
app.listen(3000, () => {
  console.log('> App is listening at port 3000...')
})

总结

挺久没有造轮子了,此次突发奇想造了个精简版的koa,虽然跟经常使用的koa框架有很大差异,然则也完成了最基本的API挪用和道理。

造轮子是一件不足为奇的事,程序员在进修过程当中不应该崇尚拿来主义,进修到肯定水平后,要秉承能作育造的立场,去尝试明白和发掘轮子背地的道理和头脑。

固然,一般来讲,自身造的轮子自身不具备多大的实用性,没有经历过社区大批的测试和现实运用场景的打磨,然则能加深自身的明白和进步自身的才也是一件值得对峙的事。

人生是一段不停攀缘的岑岭,只要对峙向前,才看到新颖的东西。

末了附上项目标
Github地点,迎接
Star或Fork支撑,感谢。

    原文作者:Mask
    原文地址: https://segmentfault.com/a/1190000018906675
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞