教你从写一个迷你koa-router到阅读koa-router源码
本打算教一步步实现koa-router,因为要解释的太多了,所以先简化成mini版本,从实现部分功能到阅读源码,希望能让你好理解一些。
希望你之前有读过koa源码,没有的话,给你链接
最核心需求-路由匹配
router最重要的就是路由匹配,我们就从最核心的入手
router.get('/string',async (ctx, next) => { ctx.body = 'koa2 string' }) router.get('/json',async (ctx, next) => { ctx.body = 'koa2 json' })
我们希望
- 路径访问 /string 页面显示 'koa2 string'
- 路径访问 /json 页面显示 'koa2 json'
先分析
收集开发者输入的信息配置
1.我们需要一个数组,数组里每个都是一个对象,每个对象包含路径,方法,函数,传参等信息
这个数组我们起个名字叫stack
const stack = []
2.对于每一个对象,我们起名叫layer
我们把它定义成一个函数
function Layer() { }
我们把页面比喻成一个箱子,箱子是对外的,箱子需要有入口,需要容纳。把每一个router比作放在箱子里的物件,物件是内部的
定义两个js页面,router.js做为入口,对于当前页面的访问的处理,layer.js包含开发者已经约定好的规则
router.js
module.exports = Router; function Router(opts) { // 容纳layer层 this.stack = []; };
layer.js
module.exports = Layer; function Layer() { };
我们在Router要放上许多方法,我们可以在Router内部挂载方法,也可以在原型上挂载函数
但是要考虑多可能Router要被多次实例化,这样里面都要开辟一份新的空间,挂载在原型就是同一份空间。
最终决定挂载在原型上
方法有很多,我们先实现约定几个常用的吧
const methods = [ 'get', 'post', 'put', 'head', 'delete', 'options', ];
methods.forEach(function(method) { Router.prototype[method] = function(path,middleware){ // 对于path,middleware,我们需要把它交给layer,拿到layer返回的结果 // 这里交给另一个函数来是实现,我们叫它register就是暂存的意思 this.register(path, [method], middleware); // 因为get还可以继续get,我们返回this return this }; });
实现layer的沟通
Router.prototype.register = function (path, methods, middleware) { let stack = this.stack; let route = new Layer(path, methods, middleware); stack.push(route); return route };
这里我们先去写layer
const pathToRegExp = require('path-to-regexp'); function Layer(path, methods, middleware) { // 把方法名称放到methods数组里 this.methods = []; // stack盛放中间件函数 this.stack = Array.isArray(middleware) ? middleware : [middleware]; // 路径 this.path = path; // 对于这个路径生成匹配规则,这里借助第三方 this.regexp = pathToRegExp(path); // methods methods.forEach(function(method) { this.methods.push(method.toUpperCase()); // 绑定layer的this,不然匿名函数的this指向window }, this); }; // 给一个原型方法match匹配返回true Layer.prototype.match = function (path) { return this.regexp.test(path); };
回到router层
定义match方法,根据Developer传入的path, method返回 一个对象(包括是否匹配,匹配成功layer,和匹配成功的方法)
Router.prototype.match = function (path, method) { const layers = this.stack; let layer; const matched = { path: [], pathAndMethod: [], route: false }; //循环寄存好的stack层的每一个layer for (var len = layers.length, i = 0; i < len; i++) { layer = layers[i]; //layer是提前存好的路径, path是过来的path if (layer.match(path)) { // layer放入path,为什么不把path传入,一是path已经没用了,匹配了就够了,layer含有更多信息需要用 matched.path.push(layer); //如果methods什么也没写,或者如果方法里含有你的过来的方法,那么把layer放入pathAndMethod if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) { matched.pathAndMethod.push(layer); // 路径匹配,并且有方法 if (layer.methods.length) matched.route = true; } } } return matched; };
给Developer一个方法
app.use(index.routes())
这里不考虑传多个id,和多次匹配情况,拿到匹配的函数
Router.prototype.routes = function(){ var router = this; const dispatch = function dispatch(ctx, next) { const path = ctx.path const method = ctx.method const matched = router.match(path, ctx.method); if (!matched.route) return next(); const matchedLayers = matched.pathAndMethod // 先不考虑多matchedLayers多stack情况 return matchedLayers[0].stack[0](ctx, next); } return dispatch }
此时一个迷你koa-router已经实现了
读源码
需求实现
实现匹配
方法名匹配,路径匹配,还要满足动态参数的传递
并且还要给很懒的开发者一个router.all()
也就是说不用区分方法了