引言
在 koa 里使用路由一般都是这样写
1 | const router = requier('koa-router') |
这样的路由设置显得不是那样的直观,而反观 Java 中 Spring 的写法,就感觉更简洁以及更优雅
1 |
|
而在 Nest 中其实已经实现了这样的写法, 因为 nest 是由 Typescript 编写的,TS 本身就支持 Decorator 的写法,再结合 reflect-metadata 来实现内部路由装饰器。
1 | @Controller('users') |
在这里,我们使用原生的 javascript 来实现在 koa 中使用装饰器路由
什么是 Decorator
首先,我们先认识一下 Decorator,它是 es6 新增的函数。
1 | @testable |
上面的代码中,@testable
就是一个修饰器,它为 MyTestableClass
这个类加上了静态属性 isTestable
这是一个最简单的例子,一般情况下,我们是需要给类的实例添加方法,所以需要在 prototype 上添加属性,对 js 的原型链不太熟悉的同学可以先看下这篇文章 详解 Javascript 的原型链与继承(从 ES5 到 ES6) , 同时可以在修饰器上传入参数
类的修饰
1 | function testable(isTestable) { |
方法的修饰
在修饰方法的时候,修饰器函数一共接收三个参数,分别是 target
(类的原型对象),name
(所要修饰的属性名),descriptor
(该属性的描述对象)
1 | class Boy { |
这里我们就简要介绍一下 Decorator,如果想要深入了解,可以看下阮一峰老师的 ES6入门
babel 转码
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API ,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。Babel 默认不转码的 API 非常多,详细清单可以查看 definitions.js 文件
而这里的 Decorator 也同样需要安装 babel 的 plugin。
如果你使用的是 babel6,那么需要安装 transform-decorators-legacy
1 | npm install --save-dev babel-preset-env transform-decorators-legacy |
.babelrc 可以这样配置
1 | { |
如果你使用的是 babel7,那么需要安装 @babel/plugin-proposal-decorators
1 | npm install --save-dev @babel/preset-env @babel/plugin-proposal-decorators |
1 | { |
之后在项目的启动文件头部,还需要引入 babel-register 和 polyfill
1 | // babel6 |
Koa 中使用装饰器路由
先看看传统的 koa 写法(不带装饰器)
1 | const koa = require('koa') |
首先,我们先实现 controller 类的装饰器
1 | const symbolPrefix = Symbol('prefix') |
这里用到了 ES6 新增的数据类型 Symbol,它表示独一无二的值。由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。
接着定义两个工具函数
formatPath
能够格式化路由,使不带 ‘/‘ 的路由带上它isArray
输出数组
1 | const formatPath = path => path.startsWith('/') ? path : `/${path}` |
设置路由的映射关系
1 | const routerMap = new Map() |
设置完后,新建一个 Route 的类,能够接收 Koa 实例和 api 文件夹路径。
1 | const Router = require('koa-router') |
再分别使用之前的 router 函数给各种请求方法配好映射
1 | const methods = {} |
除了路由方法,我们还需要对中间件也进行一次装饰器修饰
1 | const middleware = (...mids) => { |
因为可能一个路由会调用多个中间件,所以使用 ...mids
表示传进来的中间件,再将其一个个加到对应 routerMap 的前面。这样,每次发起请求时,都会先按传入的中间件顺序进行调用,之后才执行路由的方法
所以的步骤都完成之后,我们来看看最后使用装饰器的代码长什么样
1 | const { controller, get, post, middleware } = require('../lib/decorator') |
controller
, get
, post
, middleware
是我们之前对路由作了一层包装后暴露出来的接口,log
, validate
则是两个中间件
完整项目可参见源码