您现在的位置是:亿华云 > 应用开发

像Vue-Router一样配置Node路由?

亿华云2025-10-02 18:42:05【应用开发】2人已围观

简介本文转载自微信公众号「前端胖头鱼」,作者前端胖头鱼 。转载本文请联系前端胖头鱼公众号。前言前后端分离后,前端童鞋会需要处理一些node层的工作,比如模板渲染、接口转发、部分业务逻辑等,比较常用的框架有

 

本文转载自微信公众号「前端胖头鱼」,样配由作者前端胖头鱼 。样配由转载本文请联系前端胖头鱼公众号。样配由

前言

前后端分离后,样配由前端童鞋会需要处理一些node层的样配由工作,比如模板渲染、样配由接口转发、样配由部分业务逻辑等,样配由比较常用的样配由框架有koa、koa-router等。样配由

现在我们需要实现这样一个需求:

用户访问/fe的样配由时候,页面展示hello fe 用户访问/backend的样配由时候,页面展示hello backend

你是样配由不是在想,这需求俺根本不用koa、样配由koa-router,样配由原生的node模块就可以搞定。

const http = require(http) const url = require(url) const PORT = 3000 http.createServer((req, res) => {    let {  pathname } = url.parse(req.url)   let str = hello   if (pathname === /fe) {      str +=  fe   } else if (pathname === /backend) {      str +=  backend   }   res.end(str) }).listen(PORT, () => {    console.log(`app start at: ${ PORT}`) }) 

确实是,对于很简单的需求,用上框架似乎有点浪费,但是对于以上的实现,也有缺点存在,比如

需要我们自己去解析路径。 路径的香港云服务器解析和逻辑的书写耦合在一块。如果未来有更多更复杂的需求需要实现,那就gg了。

所以接下来我们来试试用koa和koa-router怎么实现

app.js

const Koa = require(koa) const KoaRouter = require(koa-router) const app = new Koa() const router = new KoaRouter() const PORT = 3000 router.get(/fe, (ctx) => {    ctx.body = hello fe }) router.get(/backend, (ctx) => {    ctx.body = hello backend }) app.use(router.routes()) app.use(router.allowedMethods()) app.listen(PORT, () => {    console.log(`app start at: ${ PORT}`) }) 

通过上面的处理,路径的解析倒是给koa-router处理了,但是整体的写法还是有些问题。

匿名函数的写法没有办法复用 路由配置和逻辑处理在一个文件中,没有分离,项目一大起来,同样是件麻烦事。

接下来我们再优化一下,先看一下整体的目录结构

├──app.js // 应用入口 ├──controller // 逻辑处理,分模块 │   ├──hello.js │   ├──aaaaa.js ├──middleware // 中间件统一注册 │   ├──index.js ├──routes // 路由配置,可以分模块配置 │   ├──index.js ├──views // 模板配置,分页面或模块处理,在这个例子中用不上 │   ├──index.html 

预览一下每个文件的逻辑

app.js 应用的路口

const Koa = require(koa) const middleware = require(./middleware) const app = new Koa() const PORT = 3000 middleware(app) app.listen(PORT, () => {    console.log(`app start at: ${ PORT}`) }) 

routes/index.js 路由配置中心

const KoaRouter = require(koa-router) const router = new KoaRouter() const koaCompose = require(koa-compose) const hello = require(../controller/hello) module.exports = () => {    router.get(/fe, hello.fe)   router.get(/backend, hello.backend)   return koaCompose([ router.routes(), router.allowedMethods() ]) } 

controller/hello.js hello 模块的逻辑

module.exports = {    fe (ctx) {      ctx.body = hello fe   },   backend (ctx) {      ctx.body = hello backend   } } 

middleware/index.js 中间件统一注册

const routes = require(../routes) module.exports = (app) => {    app.use(routes()) } 

写到这里你可能心里有个疑问?一个简单的需求,被这么一搞看起来复杂了太多,有必要这样么?

答案是:有必要,这样的目录结构或许不是站群服务器最合理的,但是路由、控制器、view层等各司其职,各在其位。对于以后的扩展有很大的帮助。

不知道大家有没有注意到路由配置这个地方

routes/index.js 路由配置中心

const KoaRouter = require(koa-router) const router = new KoaRouter() const koaCompose = require(koa-compose) const hello = require(../controller/hello) module.exports = () => {    router.get(/fe, hello.fe)   router.get(/backend, hello.backend)   return koaCompose([ router.routes(), router.allowedMethods() ]) } 

每个路由对应一个控制器去处理,很分离,很常见啊!!!这似乎也是我们平时在前端写vue-router或者react-router的常见配置模式。

但是当模块多起来的来时候,这个文件夹就会变成

const KoaRouter = require(koa-router) const router = new KoaRouter() const koaCompose = require(koa-compose) // 下面你需要require各个模块的文件进来 const hello = require(../controller/hello) const a = require(../controller/a) const c = require(../controller/c) module.exports = () => {    router.get(/fe, hello.fe)   router.get(/backend, hello.backend)   // 配置各个模块的路由以及控制器   router.get(/a/a, a.a)   router.post(/a/b, a.b)   router.get(/a/c, a.c)   router.get(/a/d, a.d)   router.get(/c/a, c.c)   router.post(/c/b, c.b)   router.get(/c/c, c.c)   router.get(/c/d, c.d)   // ... 等等       return koaCompose([ router.routes(), router.allowedMethods() ]) } 

有没有什么办法,可以让我们不用手动引入一个个控制器,再手动的调用koa-router的get post等方法去注册呢?

比如我们只需要做以下配置,就可以完成上面手动配置的功能。

routes/a.js

module.exports = [   {      path: /a/a,     controller: a.a   },   {      path: /a/b,     methods: post,     controller: a.b   },   {      path: /a/c,     controller: a.c   },   {      path: /a/d,     controller: a.d   } ] 

routes/c.js

module.exports = [   {      path: /c/a,     controller: c.a   },   {      path: /c/b,     methods: post,     controller: c.b   },   {      path: /c/c,     controller: c.c   },   {      path: /c/d,     controller: c.d   } ] 

然后使用pure-koa-router这个模块进行简单的配置就ok了

const pureKoaRouter = require(pure-koa-router) const routes = path.join(__dirname, ../routes) // 指定路由 const controllerDir = path.join(__dirname, ../controller) // 指定控制器的根目录 app.use(pureKoaRouter({    routes,   controllerDir })) 

这样整个过程我们的关注点都放在路由配置上去,再也不用去手动require一堆的文件了。

简单介绍一下上面的源码库配置

{    path: /c/b,   methods: post,   controller: c.b } 

path: 路径配置,可以是字符串/c/b,也可以是数组[ /c/b ],当然也可以是正则表达式/\c\b/

methods: 指定请求的类型,可以是字符串get或者数组[ get, post ],默认是get方法,

controller: 匹配到路由的逻辑处理方法,c.b 表示controllerDir目录下的c文件导出的b方法,a.b.c表示controllerDir目录下的/a/b 路径下的b文件导出的c方法

源码实现

接下来我们逐步分析一下实现逻辑

可以点击查看源码

整体结构

module.exports = ({  routes = [], controllerDir = , routerOptions = { } }) => {    // xxx   return koaCompose([ router.routes(), router.allowedMethods() ]) }) 

pure-koa-router接收

1.routes

可以指定路由的文件目录,这样pure-koa-router会去读取该目录下所有的文件 (const routes = path.join(__dirname, ../routes)) 可以指定具体的文件,这样pure-koa-router读取指定的文件内容作为路由配置 const routes = path.join(__dirname, ../routes/tasks.js) 可以直接指定文件导出的内容 (const routes = require(../routes/index))

2.controllerDir、控制器的根目录

3.routerOptions new KoaRouter时候传入的参数,具体可以看koa-router

这个包执行之后会返回经过koaCompose包装后的中间件,以供koa实例添加。

参数适配

assert(Array.isArray(routes) || typeof routes === string, routes must be an Array or a String) assert(fs.existsSync(controllerDir), controllerDir must be a file directory) if (typeof routes === string) {    routes = routes.replace(.js, )   if (fs.existsSync(`${ routes}.js`) || fs.existsSync(routes)) {      // 处理传入的是文件     if (fs.existsSync(`${ routes}.js`)) {        routes = require(routes)     // 处理传入的目录       } else if (fs.existsSync(routes)) {        // 读取目录中的各个文件并合并       routes = fs.readdirSync(routes).reduce((result, fileName) => {          return result.concat(require(nodePath.join(routes, fileName)))       }, [])     }   } else {      // routes如果是字符串则必须是一个文件或者目录的路径     throw new Error(routes is not a file or a directory)   } } 

路由注册

不管routes传入的是文件还是目录,又或者是直接导出的配置的内容最后的结构都是是这样的

routes内容预览

[   // 最基础的配置   {      path: /test/a,     methods: post,     controller: test.index.a   },   // 多路由对一个控制器   {      path: [ /test/b, /test/c ],     controller: test.index.a   },   // 多路由对多控制器   {      path: [ /test/d, /test/e ],     controller: [ test.index.a, test.index.b ]   },   // 单路由对对控制器   {      path: /test/f,     controller: [ test.index.a, test.index.b ]   },   // 正则   {      path: /\/test\/\d/,     controller: test.index.c   } ] 

主动注册

let router = new KoaRouter(routerOptions) let middleware routes.forEach((routeConfig = { }) => {    let {  path, methods = [ get ], controller } = routeConfig   // 路由方法类型参数适配   methods = (Array.isArray(methods) && methods) || [ methods ]   // 控制器参数适配   controller = (Array.isArray(controller) && controller) || [ controller ]   middleware = controller.map((controller) => {      // test.index.c => [ test, index, c ]     let controllerPath = controller.split(.)     // 方法名称 c     let controllerMethod = controllerPath.pop()     try {        // 读取/test/index文件的c方法       controllerMethod = require(nodePath.join(controllerDir, controllerPath.join(/)))[ controllerMethod ]     } catch (error) {        throw error     }     // 对读取到的controllerMethod进行参数判断,必须是一个方法     assert(typeof controllerMethod === function, koa middleware must be a function)     return controllerMethod   })   // 最后使用router.register进行注册   router.register(path, methods, middleware) 

源码的实现过程基本就到这里了。

结尾

pure-koa-router将路由配置和控制器分离开来,使我们将注意力放在路由配置和控制器的实现上。希望对您能有一点点帮助。

很赞哦!(6674)