十分鐘帶你了解 TypeScript Decorator

莫力全 Kyle Mo
7 min readFeb 29, 2020

--

什麼是 Decorator ?

Decorator 中文翻作裝飾器,是一種設計模式的實現,可以想像成一個 wrapper,一個用來包裝函式或類別的函式。它可以將傳入的函式或類別做特定的處理,最後再回傳修改之後的物件。透過 Decorator 可以達成在不修改原程式碼的狀況下,在執行原函式的前後做一些特定的操作,同時也把可以重複使用的邏輯切分出去。

Decorator 的種類

根據作用的目標不同,Decorator 也可以分為幾種類型

本篇文章將會介紹以下幾種 Decorator:

  1. Class Decorator
  2. Method Decorator
  3. Property Decorator
  4. Parameter Decorator

Decorator 基礎語法與概念

因為 Decorator 在 JS 中仍然為實驗性語法,如果要在自己的 TypeScript 專案中使用 Decorator,必須在 tsconfig.json 檔案中加入:

"experimentalDecorators": true

建立 Decorator 的方式很簡單,就是寫一個 function,這個 function 會有一些預設的 parameters,稍後文章中會再說明。而要使用 Decorator 的方法就是在欲被裝飾的元素上方加上 :

@裝飾者名稱

上圖的例子中,我們為我們的 Blog class 加上兩個 Decorator,這時我們打開 console 會得出以下結果:

從印出的結果可以發現 Blog class 的確被裝飾了,分別執行了兩個 Decorator 中的 console.log,然而如果眼尖的讀者可能會發現:

為什麼 decorator2 會先被印出來?

有程式基礎的人應該都很習慣一件事:程式是由上往下執行的。照理說應該會先執行 decorator1 才對,但結果卻不如我們預期,這邊要點出一個 Decorator 的重點:

Decoraotr 是由下往上執行的(Bottom-up)

還記得一開始說的,Decorator 可以看成一個 wrapper 嗎?上例中背後運作的原理就是 Blog class 先被較靠近的 decorator2 包起來,反還新的物件,再進入到 decorator1 中,經過 decorator1 處理後再反還新的物件。

Class Decorator

類別裝飾器的函式型別為 (target: any) => void 。target 即為要裝飾的類別的建構子函式(constructor),使用方式為放在 class 的上方。

console 結果

Method Decorator

方法裝飾器的函式型別為

(target: any, propertyKey: string, descriptor: PropertyDescriptor) => void。

target 代表被裝飾類別的建構子,name 代表要被裝飾的方法名稱,descriptor 參數則是儲存屬性的選項。使用發法則為放在方法的上方。

Property Decorator

欄位裝飾器的函數型別為(target: any, propertyKey: string) => void。target 為被裝飾類別的建構子,propertyKey 為被裝飾的欄位名稱。使用方式為加在欲裝飾的欄位上方。

Parameter Decorator

參數裝飾器的函式型別為

(target: any, propertyKey: string, parameterIndex: number) => void

target 為要裝飾類別的建構子,propertyKey 為用來裝飾的參數所在的方法名稱,parameterIndex 代表參數在函式中的 index (由 0 開始)。參數裝飾器的使用方法為加在參數前方。

Decorator Factory 裝飾器工廠

上面示範了不同種類的 Decorator ,有沒有辦法讓我們的 Decorator 變得更加彈性呢?例如能夠接收傳下來的參數?這時就得依賴 Decorator Factory 啦!Decorator Factory 其實就是在 Decorator 外再包一層 function,外層的 function 要回傳的則是真正的 Decorator ,這時外層接收的參數就可以在要回傳的 Decorator 中應用。

上圖中,被 return 的即是真正的 Decorator,從參數的狀況可以看出是一個 class decorator,而在前面介紹的不同 Decorator 型態,都是可以使用工廠模式的,但是ㄧ樣要注意不同種 Decorator 需要傳入的指定參數。

使用方式也很簡單,只要按照之前使用的方式,加在要裝飾的元素前,額外加上執行函式的括號就好,括號中可以傳入要帶給 Decorator 的參數。

所以說,Decorator 到底可以用在什麼地方?

有寫過 Nest.js 或是 TypeORM 的讀者或許會看過這個:

上圖是 TypeORM 定義一個 model 的寫法,當中也看見 Decorator 的身影,除了讓開發者輕易的就能定義資料欄位外,也幫助開發者在背後處理了重複的共同邏輯,讓往後程式開發變得更直覺更簡單。

實際範例

因為筆者最近在練習物件導向的寫法,因此將用 class 寫法來做範例:

這邊希望可以做到的功能是確認 class 中的 property title是必須的(required),property price 必須是一個正數,雖然是 class 的寫法,讀者也可以把應用情形想像為針對資料庫欄位的限制。那就來實作 Required 與 PositiveNumber 這兩個 Decorator 吧!這邊直接把實作後的程式碼貼上來,就不詳細解析程式碼內容了,主要是讓讀者了解 Decorator 可以應用的場合與時機。

結語

本篇文章稍微紀錄了 TypeScript Decorator 的使用方式與種類,其實現今很多第三方套件或框架,都在背後採用 Decorator ,除了讓開發者省去處理複雜且重複的邏輯的麻煩外,也讓程式碼變得更簡潔,更容易一眼就能看出程式區段的目的,將來若成為 JS 的標準語法,相信會讓 JS 開發產生巨大的改變,Let’s wait and see !

--

--