koa 源码给前端‘切’几刀之路由

前言

随着前端单页面应用的流行和复杂性的增加,前端开发需求的变化和对性能和用户体验的追求,前端开发者开始更多的关注和利用后端语言和工具协同处理。其中,前端到后端的请求和响应,其中最重要的是 url 路径的解析与处理。该路径解析和处理的主要技术是路由。

Koa 是一款基于 Node.js 平台的下一代 web 开发框架,也是时下前端框架中的重要一员。其在路由处理上,同样也有着很多值得我们一探的地方。

什么是路由

路由是一个 web 框架的基础,用于定义特定的 url 去响应请求。它将请求的 url 映射到指定的回调函数上。在路由处理当中,有两个重要的概念是“路由”和“路由器”。“路由”用于表述单个 url 和对应的处理函数,而“路由器”则是建立多个路由和执行路由的机制。

koa-router 的实现方式

Koa 官方推荐的路由处理第三方中间件是“koa-router”。这个库本身非常的轻量级,只含有 390 行的代码,但却制作精良,功能强大。接下来,我们主要看一下 koa-router 是怎么实现的。

实现一:正则表达式匹配路由

koa-router 采用的实现方式是利用正则表达式来匹配路由。其基本思路是将 url 路径进行解析成正则表达式,再用正则表达式来匹配路由,从而达到与处理 url 请求的目的。

例如,以下的示例代码,定义了任意请求方法的根路由,其代码实现如下:

router.all('/', (ctx, next) => {
    // 处理根路由请求
});

在 koa-router 内部,会将该 url 路径转化成了以下代码的正则表达式进行路由匹配:

/^\/?(?=\/|$)/i

可以看到,这个正则表达式的含义是:

  • ^\/? : 匹配 url 以 / 开头或不以 / 开头的情况。
  • (?=\/|$) : 匹配接下来的字符是 / 还是 PATH,如果是 / 就结束匹配,否则匹配到结束。

实现二:树节点搜索路由

另外一种 koa-router 的实现方式是,将路由按照 / 拆分成多个节点,将节点转化成“前缀树”(Trie),然后根据每个 url 部分的路径节点构建树结构来匹配路由。

koa-router 实现的类似前缀树的树结构,内部是基于 hash 的对象来存储每个路径的子节点和路径处理函数,数据结构如下:

{  
   "path":{
      "path":{
         "path":{
            "path":{
               "methods":{
                  "all":[Function]
               },
               "stack":[
                  ...
               ]
            },
            "methods":{
               "get":[Function],
               "post":[Function]
            }
         }
      }
   }
}

该数据结构实现的路由匹配处理过程如下:

// 获取路由前缀树
const router = new Router();
router.get('/user/:id', (ctx, next) => {});
const tree = router.stack.reduce((acc, layer) => {
  const routerPath = layer.path;
  const methodsObj = layer.methods;

  if (methodsObj) {
    Object.keys(methodsObj).forEach((method) => {
      if (!acc[method]) {
        acc[method] = {};
      }

      acc[method][routerPath] = layer;
    });
  }

  return acc;
}, {});

// 加入前缀树的节点判断
const match_router = function (uri, tree) {
    let uris = uri.split('/').filter(i => i)
    let recent = null;
    let node = tree;
    while (node) {
        let part = uris.shift() || '/'

        if (node[part] !== undefined) {
            node = node[part];
            if (!uris.length) {
                return node;
            }
        }
        else if (node[':']) {
            recent = node[':'];
            node = node[':'];
            recent.param = {};
            recent.param.id = part;
            if (!uris.length) {
                return node;
            }
        }
        else if (node['*']) {
            return node['*'];
        }
        else {
            return null;
        }
    }
    return null;
}

match_router('/user/2333', tree) // { methods: { get: [Function] }, path: '/user/:id', param: { id: 2333 } }

需要注意的是,在树节点搜索路由实现方式中,路由匹配的顺序非常重要。在 koa-router 的实现中,路由匹配是根据前缀树的搜索顺序来匹配路由的。例如,如果 url 请求的是 /user/2333 会和 /user/:userId 的路由匹配,而不会和 /user/*** 进行匹配。

总结

koa-router 作为一款灵活实用的路由中间件,在处理路由匹配的时候采用了两种不同的实现方式:通过正则表达式进行匹配和通过树节点的搜索路由。在使用上,我们可以根据项目实际情况来选择适用的实现方式,并对其细节进行优化和改进,从而使得前端的路由处理更加高效、方便和灵活。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/659501fbeb4cecbf2d943ea5


纠错反馈