TS裝飾器初體驗晤柄,用裝飾器管理koa接口

typescript中的裝飾器有很多種擦剑,比如類裝飾器、方法裝飾器可免、屬性裝飾器等等抓于,先看看裝飾器的定義吧,下面以類裝飾器和方法裝飾器為例子浇借,順便說幾個點

類裝飾器
// 裝飾器都是以@符加在類或方法上面
@tsetDecorator
class Test {
  name = '超人鴨'
}
// 裝飾器都是函數(shù)
function tsetDecorator(target: any) {
  console.log('裝飾器')
}

現(xiàn)在我是沒有創(chuàng)建這個類的實例的捉撮,只是聲明,當我執(zhí)行這個文件時就會打印出'裝飾器'
從這個最簡單的例子說幾個點

  1. 類的裝飾器有一個參數(shù)妇垢,為類的構(gòu)造函數(shù)巾遭,通過這個參數(shù)可以改變類上的屬性方法等
  2. 類的裝飾器會在類定義后執(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é)合上面的例子講一下方法裝飾器涉及的點

  1. 和類裝飾器一樣,在類定義時方法裝飾器就會執(zhí)行
  2. 方法裝飾器的三個參數(shù)献丑,普通方法裝飾器的第一個參數(shù)為類的prototype,靜態(tài)方法裝飾器的第一個參數(shù)為類的構(gòu)造函數(shù)侠姑;第二個參數(shù)為方法的名稱;第三個參數(shù)類似于object.defineproperty莽红,可以對方法自身作某些修改邦邦,像上面例子一樣,可以改變方法是否可改燃辖,以及方法本身。
  3. 當一個類有類裝飾器和方法裝飾器同時存在郭赐,執(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
  1. 用這個庫直接import就可以了
  2. 存數(shù)據(jù)豁状,通過Reflect.defineMetadata存儲,第一個參數(shù)為存的數(shù)據(jù)的key倒得,第二個參數(shù)為數(shù)據(jù)的值泻红,第三個參數(shù)為存到哪個對象上(類),如果是存在方法上霞掺,那就有第四個參數(shù)谊路,為存放的方法的方法名稱。
  3. 取數(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('服務開啟在三千端口')
})

測試:


image.png

image.png

image.png

無問題遏餐,這就是我學了ts裝飾器的小總結(jié)demo失都,如果你有更好的看法見解幸冻,歡迎指教哦

作者微信:Promise_fulfilled
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洽损,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子流码,更是在濱河造成了極大的恐慌延刘,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驾荣,死亡現(xiàn)場離奇詭異播掷,居然都是意外死亡撼班,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門件炉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事板祝。” “怎么了孤里?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵捌袜,是天一觀的道長炸枣。 經(jīng)常有香客問我,道長霍衫,這世上最難降的妖魔是什么侯养? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任逛揩,我火速辦了婚禮,結(jié)果婚禮上辩稽,老公的妹妹穿的比我還像新娘。我一直安慰自己逞泄,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布并级。 她就那樣靜靜地躺著侮腹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愈涩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天煤篙,我揣著相機與錄音毁腿,去河邊找鬼。 笑死鸠窗,一個胖子當著我的面吹牛胯究,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臣嚣,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茧球,長吁一口氣:“原來是場噩夢啊……” “哼星持!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起督暂,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逻翁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酷愧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缠诅,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡管引,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谅将。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饥臂。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡稽煤,死狀恐怖念脯,靈堂內(nèi)的尸體忽然破棺而出弯淘,到底是詐尸還是另有隱情吉懊,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布态鳖,位于F島的核電站恶导,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邦泄。R本人自食惡果不足惜裂垦,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望特碳。 院中可真熱鬧晕换,春花似錦、人聲如沸闸准。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颂暇。三九已至,卻和暖如春耳鸯,著一層夾襖步出監(jiān)牢的瞬間膀曾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工财喳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斩狱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓泌枪,卻偏偏與公主長得像秕岛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子修壕,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容