typescript中的裝飾器有很多種擦剑,比如類裝飾器、方法裝飾器可免、屬性裝飾器等等抓于,先看看裝飾器的定義吧,下面以類裝飾器和方法裝飾器為例子浇借,順便說幾個點
類裝飾器
// 裝飾器都是以@符加在類或方法上面
@tsetDecorator
class Test {
name = '超人鴨'
}
// 裝飾器都是函數(shù)
function tsetDecorator(target: any) {
console.log('裝飾器')
}
現(xiàn)在我是沒有創(chuàng)建這個類的實例的捉撮,只是聲明,當我執(zhí)行這個文件時就會打印出'裝飾器'
從這個最簡單的例子說幾個點
- 類的裝飾器有一個參數(shù)妇垢,為類的構(gòu)造函數(shù)巾遭,通過這個參數(shù)可以改變類上的屬性方法等
- 類的裝飾器會在類定義后執(zhí)行,不需要類實例化
再看一個簡單的例子:
@tsetDecorator
class Test {
}
function tsetDecorator(target: any) {
target.prototype.name = '吳彥祖'
}
console.log(Test.prototype) // {name:'吳彥祖'}
裝飾器的工廠模式
裝飾器是不可以直接傳遞參數(shù)的闯估,但是之前看到vue里面用到裝飾器的時候灼舍,好像都是可以傳遞參數(shù)的,比如prop裝飾器:
@Prop(Boolean)
private visible: boolean | undefined
其實都是用到工廠模式涨薪,以上面設(shè)置name的例子骑素,改寫一下:
@tsetDecorator('吳彥祖')
class Test {
}
function tsetDecorator(name: string) {
return function (target: any) {
target.prototype.name = name
}
}
console.log(Test.prototype) // {name:'吳彥祖'}
方法裝飾器
直接上例子:
class Test {
name = '超人鴨'
@tsetDecorator
getName() {
return this.name
}
}
function tsetDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.value = function () {
return '吳彥祖'
}
descriptor.writable = false
}
const test = new Test()
console.log(test.getName()) // 吳彥祖
test.getName = () => { // 報錯
return '111'
}
結(jié)合上面的例子講一下方法裝飾器涉及的點
- 和類裝飾器一樣,在類定義時方法裝飾器就會執(zhí)行
- 方法裝飾器的三個參數(shù)献丑,普通方法裝飾器的第一個參數(shù)為類的prototype,靜態(tài)方法裝飾器的第一個參數(shù)為類的構(gòu)造函數(shù)侠姑;第二個參數(shù)為方法的名稱;第三個參數(shù)類似于object.defineproperty莽红,可以對方法自身作某些修改邦邦,像上面例子一樣,可以改變方法是否可改燃辖,以及方法本身。
- 當一個類有類裝飾器和方法裝飾器同時存在郭赐,執(zhí)行的順序是先執(zhí)行方法裝飾器再執(zhí)行類裝飾器
上面所說的就是裝飾器一些基本的概念,下面再介紹一個庫确沸,通過這個庫配合裝飾器來管理koa的接口
reflect-metadata
這個庫可以往類或方法上添加元數(shù)據(jù)捌锭,這個數(shù)據(jù)簡單來說就是存在的,但是直接拿是拿不到的罗捎,得通過它的api存數(shù)據(jù)與取數(shù)據(jù)观谦,先別懵逼,看到最后配合裝飾器的使用就知道這個庫的用處了桨菜。
import 'reflect-metadata'
class Test {
getName() {
}
}
// 存數(shù)據(jù)
Reflect.defineMetadata('data', 'test', Test)
Reflect.defineMetadata('data', 'test', Test, 'getName')
// 取數(shù)據(jù)
console.log(Reflect.getMetadata('data', Test)) // test
console.log(Reflect.getMetadata('data', Test, 'getName')) // test
- 用這個庫直接import就可以了
- 存數(shù)據(jù)豁状,通過Reflect.defineMetadata存儲,第一個參數(shù)為存的數(shù)據(jù)的key倒得,第二個參數(shù)為數(shù)據(jù)的值泻红,第三個參數(shù)為存到哪個對象上(類),如果是存在方法上霞掺,那就有第四個參數(shù)谊路,為存放的方法的方法名稱。
- 取數(shù)據(jù)菩彬,通過Reflect.getMetadata取數(shù)據(jù)缠劝,第一個參數(shù)為數(shù)據(jù)的key,第二個參數(shù)為存放的對象(類)骗灶,如果是存在方法上惨恭,那第三個參數(shù)為存放的方法的方法名稱。
這就是reflect-metadata的基礎(chǔ)使用方法耙旦,接下來結(jié)合裝飾器對koa接口進行管理脱羡,先看看普通寫koa接口的寫法:
import Router from 'koa-router'
const router = new Router()
router.get('/', async (ctx) => {
ctx.body = '訪問根路徑'
})
router.get('/testGet', async (ctx) => {
ctx.body = '這個是get請求'
})
router.post('/testPost', async (ctx) => {
ctx.body = '這個是post請求'
})
export default router
正常寫koa接口時,都是按模塊分類然后加上路由前綴免都,比如用戶模塊锉罐、訂單模塊等,像我上面的例子琴昆,三個接口當成是一個模塊氓鄙,那我們就可以把一個模塊寫成一個類來管理馆揉,先看改寫后的類:
import Application from 'koa' // koa-router中的一個類型
@controller
class TestController{
@get('/')
async home(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body =
`
<html>
<body>
<form method="post" action="/testPost">
<button>post請求</button>
</form>
<form method="get" action="/testGet">
<button>get請求</button>
</form>
</body>
</html>
`
}
@post('/testPost')
async testPost(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body = '這是post請求'
}
@get('/testGet')
async testGet(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body = '這是get請求'
}
}
我把原來三個router接口改寫成類中的三個方法业舍,路徑用裝飾器進行傳入,我的目的就是達到與普通寫法一樣,生成三個router接口舷暮,訪問的路徑與請求方法都一致态罪,所以實現(xiàn)都放到裝飾器里面,先看方法裝飾器下面,也就是get和post方法:
function get(path: string){
// 往方法上存上路徑與請求方法
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
Reflect.defineMetadata('method', 'get', target, key)
}
}
function post(path: string){
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
Reflect.defineMetadata('method', 'post', target, key)
}
}
// 這兩個方法都是高度相似的复颈,可以再做一層封裝,所以上面兩個方法刪掉沥割,改成下面:
enum Methods { // 定義一個枚舉類型
get = 'get',
post = 'post'
}
function getRequestDecorator(method: Methods) {
return function (path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
Reflect.defineMetadata('method', method, target, key)
}
}
}
const get = getRequestDecorator(Methods.get)
const post = getRequestDecorator(Methods.post)
這樣就給每個方法都綁定了對應的路徑與請求方法,這里用到了枚舉類型帜讲,用它來做類型校驗似将,這種使用方式非常適合某個變量只能是固定的幾個值得情況蚀苛,像http請求,就是get腋舌、post渗蟹、put等,不能是其他的刨沦。
到這里該綁定的都綁定了想诅,接下來就是生成router的接口岛心,我們在類的裝飾器controller操作,因為方法執(zhí)行器是先于類執(zhí)行器徘禁,所以在類執(zhí)行器里面可以操作到剛剛在方法裝飾器中綁定的數(shù)據(jù):
function controller(target: any) {
for (let key in target.prototype) { // 遍歷類上的方法
const path: string = Reflect.getMetadata('path', target.prototype, key) // 拿到路徑
const method: Methods = Reflect.getMetadata('method', target.prototype, key) // 獲取請求方法
const hanlder = target.prototype[key] // 獲取方法
if (path && method && hanlder) {
router[method](path, hanlder) // 注冊router接口
}
}
}
到這里router接口就都注冊完成了送朱,下面是完整代碼:
import Router from 'koa-router'
const router = new Router()
import Application from 'koa'
import 'reflect-metadata'
enum Methods {
get = 'get',
post = 'post'
}
function getRequestDecorator(method: Methods) {
return function (path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
Reflect.defineMetadata('method', method, target, key)
}
}
}
const get = getRequestDecorator(Methods.get)
const post = getRequestDecorator(Methods.post)
function controller(target: any) {
for (let key in target.prototype) {
const path: string = Reflect.getMetadata('path', target.prototype, key)
const method: Methods = Reflect.getMetadata('method', target.prototype, key)
const hanlder = target.prototype[key]
if (path && method && hanlder) {
router[method](path, hanlder)
}
}
}
@controller
class TestController {
@get('/')
async home(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body =
`
<html>
<body>
<form method="post" action="/testPost">
<button>post請求</button>
</form>
<form method="get" action="/testGet">
<button>get請求</button>
</form>
</body>
</html>
`
}
@post('/testPost')
async testPost(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body = '這是post請求'
}
@get('/testGet')
async testGet(ctx: Application.ParameterizedContext<any, Router.IRouterParamContext<any, {}>>) {
ctx.body = '這是get請求'
}
}
export default router
接下來在koa的入口頁驶沼,一般叫app.ts中引入這個router,下面是我的app.ts的全部代碼
import Koa from 'koa'
const app = new Koa()
import Router from 'koa-router'
const router = new Router()
import testRouter from './controller/test' // 引入文件大年,class會自動定義
router.use('', testRouter.routes()) // 第一個參數(shù)為接口前綴翔试,這里無前綴
app.use(router.routes())
app.use(router.allowedMethods()) // 允許http請求的所有方法
app.listen(3000, () => {
console.log('服務開啟在三千端口')
})
測試:
無問題遏餐,這就是我學了ts裝飾器的小總結(jié)demo失都,如果你有更好的看法見解幸冻,歡迎指教哦