隨著應用的龐大,專案中 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 精通指南》