上一期链接——也就是本文的基础,参考KOA,5步手写一款粗糙的web框架

本文参考仓库:点我

Router其实就是路径匹配,通过匹配路径,返回给用户相应的网站内容。

以下方例子为例,主要通过提取req中的path信息,来匹配当前路径,并给ctx.body赋值,返回相应的界面。这个过程不复杂,就是一个匹配路径的过程。但是这种会不会太臃肿了呢,而且很有可能路径一多,就要被if...else...给弄晕了。

  1. app.use((ctx,next)=>{
  2. //简易路由
  3. let {path}=ctx
  4. if(path==="/"){
  5. ctx.body="index"
  6. }else if(path==="/admin"){
  7. ctx.body="admin"
  8. }else if(path==="/user"){
  9. ctx.body="user"
  10. }
  11. })

这个时候专门处理路径的插件就出现了,写一个Router,专门用来管理路径。

Router的功能一共是两个:

  • 匹配路径
  • 返回相应页面

如果Router要挂载到app上,那么语法是这样的app.use(router.routes()),也就是说:

  • Router本身就是个中间件
  • 为了返回匹配的路由,写一个中间件挂到app

了解了Router的大概,我们开始一步步动手写Router吧!

先把Router的框架写好,一个构造器,一个get方法用于配置路由,一个routers变成路由匹配的中间件挂在到app上。

  1. class Router{
  2. constructor(){}
  3. get(path,callback){}
  4. routers(){}
  5. }

我们获取路由的时候,一定会配置页面,那么这个页面的类也要加上了,每次get的时候,就加入一个页面到数组中。

  1. class Page{
  2. constructor(path,callback){
  3. this.path=path
  4. this.callback=callback
  5. }
  6. }
  7. class Router{
  8. constructor(){
  9. this.pages=[]
  10. }
  11. get(path,callback){
  12. this.pages.push(new Page(path,callback))
  13. }
  14. routers(){}
  15. }

因为路由是对中间件的封装,所以用法上是和app.use类似的:

  1. router.get(path,(ctx,next){
  2. ctx.body='xxx'
  3. next()
  4. })

是不是很眼熟?这个get中的callback参数就是中间件。

routers就干三件事:

  • 筛选出匹配的路由,array.filter就可以做到
  • 组合执行这些路由
  • 返回一个中间件
  1. compose(ctx,next,routers){
  2. function dispatch(index){
  3. if(index===routers.length) return next();
  4. let router=routers[index]
  5. router(ctx,()=>dispatch(index+1));
  6. }
  7. dispatch(0)
  8. }
  9. routers(){
  10. let dispatch = (ctx,next)=>{
  11. let path=ctx.path
  12. let routers=this.pages.filter(p=>{console.log(p.path);return p.path===path}).map(p=>p.callback)
  13. this.compose(ctx,next,routers)
  14. }
  15. return dispatch
  16. }

大家有没有很眼熟,和koa中的application.js的回调很像。其实就是一个回调的过程,封装之后,便于我们使用。

我们再写路由的时候,如果全部写全路径,感觉会很啰嗦:

  1. router.get("/admin",(ctx,next)=>{})
  2. router.get("/admin/login",(ctx,next)=>{})
  3. router.get("/admin/register",(ctx,next)=>{})
  4. ...
  5. router.get("/user",(ctx,next)=>{})
  6. router.get("/user/login",(ctx,next)=>{})
  7. router.get("/user/register",(ctx,next)=>{})
  8. ....

我们给路由分组,其实思路很简单,就是给每个小路由新建一个Router,然后大路由用use方法,将这些路由集合到一起。

  1. let admin=new Router()
  2. admin.get("/",(ctx,next)=>{
  3. ctx.body="admin"
  4. next()
  5. })
  6. let user=new Router()
  7. user.get("/",(ctx,next)=>{
  8. ctx.body="user"
  9. next()
  10. })
  11. //链式调用~
  12. let router=new Router()
  13. router.use("/admin",admin.routers())
  14. .use("/user",user.routers())
  15. app.use(router.routers())

那么问题来了,use要怎么写呢才能组合这些routers??我们先来分析下use的功能:

  • 组合路径
  • 将route加入当前对象的数组中

use中有两个参数一个path,一个router.routers()的中间件,可是我们需要router数组对象,所以我们可以这么做:

  1. routers(){
  2. let dispatch = (ctx,next)=>{
  3. .....
  4. }
  5. dispatch.router=this
  6. return dispatch
  7. }

在中间件上暗搓搓地加一个router的对象,将自己一起传递出去,有么有很机智

有了router的数组对象,那么use这个方法就很好实现了,将page循环一波,加入当前对象的pages,就好了。这里再将自己返回,然后就可以愉快地使用链式调用了。

  1. use(path,middleware) {
  2. let router = this;
  3. middleware.router.pages.forEach(p => {
  4. router.get(path+p.path,p.callback)
  5. });
  6. return router
  7. }

大家需要注意,还记得上一期讲的async/await异步吗?

如果有任何除了路由的操作都要放在路由上方执行,因为路由只是匹配路径,返回结果,并没有async/await操作。

所以一定注意:

这样是有效的·,页面返回aaa

  1. app.use(async (ctx,next)=>{
  2. await makeAPromise(ctx).then(()=>{next()})
  3. })
  4. ...
  5. app.use(router.routers())

这样是无效的,页面不会返回aaa

  1. ...
  2. app.use(router.routers())
  3. app.use(async(ctx,next)=>{
  4. await next()//等待下方完成后再继续执行
  5. ctx.body="aaa"
  6. })

版权声明:本文为cherryvenus原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/cherryvenus/p/9542882.html