手写@koa/router源码 (3)

另外我们看到他也用到了path-to-regexp这个库,这个库我在很多处理路由的库里面都见到过,比如React-Router,Express,真想去看看他的源码,加到我的待写文章列表里面去,空了去看看~

Layer构造函数官方源码:

router.routes()

前面架构提到的还有件事情需要做,那就是路由匹配

对于Koa来说,一个请求来了会依次经过每个中间件,所以我们的路由匹配其实也是在中间件里面做的。而@koa/router的中间件是通过router.routes()返回的。所以router.routes()主要做两件事:

他应该返回一个Koa中间件,以便Koa调用

这个中间件的主要工作是遍历router上的layer,找到匹配的路由,并拿出来执行。

Router.prototype.routes = function () { const router = this; // 这个dispatch就是我们要返回给Koa调用的中间件 let dispatch = function dispatch(ctx, next) { const path = ctx.path; const matched = router.match(path, ctx.method); // 获取所有匹配的layer let layerChain; // 定义一个变量来串联所有匹配的layer ctx.router = router; // 顺手把router挂到ctx上,给其他Koa中间件使用 if (!matched.route) return next(); // 如果一个layer都没匹配上,直接返回,并执行下一个Koa中间件 const matchedLayers = matched.pathAndMethod; // 获取所有path和method都匹配的layer // 下面这段代码的作用是将所有layer上的stack,也就是layer的回调函数都合并到一个数组layerChain里面去 layerChain = matchedLayers.reduce(function (memo, layer) { return memo.concat(layer.stack); }, []); // 这里的compose也是koa-compose这个库,源码在讲Koa源码的时候讲过 // 使用compose将layerChain数组合并成一个可执行的方法,并拿来执行,传入参数是Koa中间件参数ctx, next return compose(layerChain)(ctx, next); }; // 将中间件返回 return dispatch; };

上述代码中主体返回的是一个Koa中间件,这个中间件里面先是通过router.match方法将所有匹配的layer拿出来,然后将这些layer对应的回调函数通过reduce放到一个数组里面,也就是layerChain。然后用koa-compose将这个数组合并成一个可执行方法,这里就有问题了。之前在Koa源码解析我讲过koa-compose的源码,这里再大致贴一下:

function compose(middleware) { // 参数检查,middleware必须是一个数组 if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!"); // 数组里面的每一项都必须是一个方法 for (const fn of middleware) { if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!"); } // 返回一个方法,这个方法就是compose的结果 // 外部可以通过调用这个方法来开起中间件数组的遍历 // 参数形式和普通中间件一样,都是context和next return function (context, next) { return dispatch(0); // 开始中间件执行,从数组第一个开始 // 执行中间件的方法 function dispatch(i) { let fn = middleware[i]; // 取出需要执行的中间件 // 如果i等于数组长度,说明数组已经执行完了 if (i === middleware.length) { fn = next; // 这里让fn等于外部传进来的next,其实是进行收尾工作,比如返回404 } // 如果外部没有传收尾的next,直接就resolve if (!fn) { return Promise.resolve(); } // 执行中间件,注意传给中间件接收的参数应该是context和next // 传给中间件的next是dispatch.bind(null, i + 1) // 所以中间件里面调用next的时候其实调用的是dispatch(i + 1),也就是执行下一个中间件 try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))); } catch (err) { return Promise.reject(err); } } }; }

这段代码里面fn是我们传入的中间件,在@koa/router这里对应的其实是layerChain里面的一项,执行fn的时候是这样的:

fn(context, dispatch.bind(null, i + 1))

这里传的参数符合我们使用@koa/router的习惯,我们使用@koa/router一般是这样的:

router.get("http://www.likecs.com/", (ctx, next) => { ctx.body = "Hello World"; });

上面的fn就是我们传的回调函数,注意我们执行fn时传入的第二个参数dispatch.bind(null, i + 1),也就是router.get这里的next。所以我们上面回调函数里面再执行下next:

router.get("http://www.likecs.com/", (ctx, next) => { ctx.body = "Hello World"; next(); // 注意这里 });

这个回调里面执行next()其实就是把koa-compose里面的dispatch.bind(null, i + 1)拿出来执行,也就是dispatch(i + 1),对应的就是执行layerChain里面的下一个函数。在这个例子里面并没有什么用,因为匹配的回调函数只有一个。但是如果/这个路径匹配了多个回调函数,比如这样:

router.get("http://www.likecs.com/", (ctx, next) => { console.log("123"); }); router.get("http://www.likecs.com/", (ctx, next) => { ctx.body = "Hello World"; });

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsjzz.html