随着应用的庞大,项目中 JavaScript 的代码也会越来越臃肿,这时候许多 JavaScript 的语言弊端就会愈发明显,而 TypeScript 的出现,就是着力于解决 JavaScript 语言天生的弱势:静态类型。
前端开发 QQ 群:377786580
这篇文章首发于我的个人博客 《听说》,系列目录:
- 《从 JavaScript 到 TypeScript 1 - 什么是 TypeScript》
- 《从 JavaScript 到 TypeScript 2 - 基础特性和类型推导》
- 《从 JavaScript 到 TypeScript 3 - 引入和编译》
- 《从 JavaScript 到 TypeScript 4 - 装饰器和反射》
- 《从 JavaScript 到 TypeScript 5 - express 路由进化》
- 《从 JavaScript 到 TypeScript 6 - vue 引入 TypeScript》
在上一篇文章 《从 JavaScript 到 TypeScript 4 - 装饰器和反射》 我们介绍了装饰器和反射,在这篇文章中,我们会把这两个特性引入,并且在 express 上,实现一层全新的路由封装。
express 路由
首先我们来看一个简单的 express
路由 (router):
// 对网站首页的访问返回 "Hello World!"
app.get('/', function (req: Request, res: Reponse) {
res.send('Hello World!')
})
app.post('/user', function (req: Request, res: Reponse) {
res.send(`User Id ${req.query.id}`)
})
在上面的路由代码我们演示了一个普通流水线式的路由。
基于上一篇文章中我们学到的装饰器和反射的知识,我们将要实现 路由的配置通过装饰器实现,并且实现一层路由逻辑的封装。
路由进化
基于装饰器和反射,我们要实现的路由最终效果是这样的:
class Home {
@path('/user')
@httpGet
user (id: string) {
return `User Id ${id}`
}
}
GET HTTP/1.1
Host: /user?id=tasaid.com
这段代码相比传统的路由配置,优点如下:
- 将路由的配置抽离成为了装饰器,让整个
router
函数内部只需要处理业务逻辑即可,路由配置简单明了 - 隐藏
req
和res
,每个router
直接返回结果即可,无需自己再输出结果
装饰器: HTTP Method
我们先编写 HTTP Method
的装饰器,我们将实现两个装饰器,分别叫做 httpGet
和 httpPost
,对应 HTTP Method 的 GET/POST
。
原理上,我们会将 router
配置的数据都挂到使用装饰器的方法上。
import 'reflect-metadata'
export const symbolHttpMethodsKey = Symbol("router:httpMethod")
export const httpGet = function (target: any, propertyKey: string) {
// 挂载到调用装饰器的方法上
Reflect.defineMetadata(symbolHttpMethodsKey, 'get', target, propertyKey)
}
export const httpPost = function (target: any, propertyKey: string) {
Reflect.defineMetadata(symbolHttpMethodsKey, 'post', target, propertyKey)
}
装饰器: path
有了上面 HTTP Method
装饰器的实现,我们再实现 path
装饰器将会很简单。
当然,我们还可以在 path
中实现对原方法的封装:隐藏 req
和 res
,并对 router 的输出结果进行封装。
注意这里使用的是装饰器工厂:
import 'reflect-metadata'
export const symbolPathKey = Symbol.for('router:path')
export let path = (path: string): Function => {
return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<Function>) {
Reflect.defineMetadata(symbolPathKey, path, target, propertyKey)
if (!descriptor.value) return
// 覆盖掉原来的 router method,在外层做封装
let oldMethod = descriptor.value
descriptor.value = function (req: Request, res: Response) {
const params = Object.assign({}, req.body, req.query)
let methodResult = oldMethod.call(this, params)
// 输出返回结果
res.send(methodResult)
}
}
}
Router? Controller!
现在,我们需要将所有的 Router 按照自己的业务规则/或者自定义的其他规则进行归类 —— 然后提取出对应的 Class
,例如下面的 User Class
就是把用户信息所有的 router
都归类在一起:
class User {
@httpPost
@path('/user/login')
login() { }
@httpGet
@path('/user/exit')
exit() { }
}
然后在 express
配置的入口逻辑那里,把 class
对应的方法遍历一遍,然后使用 reflect-metadata
反射对应的 router
配置即可:
import 'reflect-metadata'
// 装饰器挂载数据的 key
import { symbolHttpMethodsKey, symbolPathKey } from './decorators'
const createController = (app: Express) => {
let user = new User()
for (let methodName in user) {
let method = user[methodName]
if (typeof method !== 'function') break
// 反射得到挂载的数据
let httpMethod = Reflect.getMetadata(symbolHttpMethodsKey, user, methodName)
let path = Reflect.getMetadata(symbolPathKey, user, methodName)
// app.get('/', () => any)
app[httpMethod](path, method)
}
}
至此,我们的 express
路由进化完毕,效果如下:
完整的例子可以参考我的 Github。
结语
装饰器目前在 ECMAScript 新提案中的 建议征集的第二阶段(Stage 2),由于装饰器在其他语言中早已实现,例如 Java 的注解(Annotation) 和 C# 的特性(Attribute),所以纳入 ECMAScript 规范只是时间问题了。
装饰器来装饰路由,并且封装 router
操作的的思路缘起 .NET MVC
架构:
angular 2.x
使用也引入了装饰器作为核心开发,随着规范的推进,相信装饰器进入大家视野,应用的场景也会越来越多。
在下一篇文章 《从 JavaScript 到 TypeScript 6 - Vue 引入 TypeScript》 中,我们将介绍如何在 Vue 中引入 TypeScript。
TypeScript 中文网:https://tslang.cn/
TypeScript 视频教程:《TypeScript 精通指南》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。