TypeScript基礎入門之裝飾器(三)
繼續上篇文章[ofollow,noindex">TypeScript基礎入門之裝飾器(二) ]
訪問器裝飾器
Accessor Decorator在訪問器宣告之前宣告。 訪問器裝飾器應用於訪問器的屬性描述符,可用於觀察,修改或替換訪問者的定義。 訪問器裝飾器不能在宣告檔案中使用,也不能在任何其他環境上下文中使用(例如在宣告類中)。
注意: TypeScript不允許為單個成員裝飾get和set訪問器。相反,該成員的所有裝飾器必須應用於按文件順序指定的第一個訪問器。這是因為裝飾器適用於屬性描述符,它結合了get和set訪問器,而不是單獨的每個宣告。
訪問器裝飾器的表示式將在執行時作為函式呼叫,具有以下三個引數:
- 靜態成員的類的建構函式,或例項成員的類的原型。
- 成員的名字。
- 會員的財產描述。
注意: 如果指令碼目標小於ES5,則屬性描述符將不確定。
如果訪問器裝飾器返回一個值,它將用作該成員的屬性描述符。
注意: 如果指令碼目標小於ES5,則忽略返回值。
以下是應用於Point類成員的訪問器裝飾器(@configurable )的示例:
class Point { private _x: number; private _y: number; constructor(x: number, y: number) { this._x = x; this._y = y; } @configurable(false) get x() { return this._x; } @configurable(false) get y() { return this._y; } }
我們可以使用以下函式宣告定義@configurable裝飾器:
function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; }; }
屬性裝飾器
Property Decorator在屬性宣告之前宣告。 屬性修飾器不能在宣告檔案中使用,也不能在任何其他環境上下文中使用(例如在宣告類中)。
屬性裝飾器的表示式將在執行時作為函式呼叫,具有以下兩個引數:
- 靜態成員的類的建構函式,或例項成員的類的原型。
- 成員的名字。
注意: 由於在TypeScript中如何初始化屬性裝飾器,因此不提供屬性描述符作為屬性裝飾器的引數。這是因為在定義原型的成員時,當前沒有機制來描述例項屬性,也無法觀察或修改屬性的初始化程式。返回值也會被忽略。因此,屬性裝飾器只能用於觀察已為類宣告特定名稱的屬性。
我們可以使用此資訊來記錄有關屬性的元資料,如以下示例所示:
class Greeter { @format("Hello, %s") greeting: string; constructor(message: string) { this.greeting = message; } greet() { let formatString = getFormat(this, "greeting"); return formatString.replace("%s", this.greeting); } }
然後我們可以使用以下函式宣告定義@format裝飾器和getFormat函式:
import "reflect-metadata"; const formatMetadataKey = Symbol("format"); function format(formatString: string) { return Reflect.metadata(formatMetadataKey, formatString); } function getFormat(target: any, propertyKey: string) { return Reflect.getMetadata(formatMetadataKey, target, propertyKey); }
這裡的@format ("Hello,%s")裝飾器是一個裝飾工廠。 當呼叫@format ("Hello,%s")時,它會使用reflect-metadata庫中的Reflect.metadata函式為該屬性新增元資料條目。 呼叫getFormat時,它會讀取格式的元資料值。
注意此示例需要reflect-metadata庫。 有關reflect-metadata庫的更多資訊,請參閱元資料。
引數裝飾器
引數裝飾器在引數宣告之前宣告。 引數裝飾器應用於類建構函式或方法宣告的函式。 引數裝飾器不能用於宣告檔案,過載或任何其他環境上下文(例如宣告類中)。
引數裝飾器的表示式將在執行時作為函式呼叫,具有以下三個引數:
- 靜態成員的類的建構函式,或例項成員的類的原型。
- 成員的名字。
- 函式引數列表中引數的序數索引。
注意: 引數裝飾器只能用於觀察已在方法上宣告引數。
將忽略引數裝飾器的返回值。
以下是應用於Greeter類成員引數的引數裝飾器(@required )的示例:
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } @validate greet(@required name: string) { return "Hello " + name + ", " + this.greeting; } }
然後我們可以使用以下函式宣告定義@required和@validate裝飾器:
import "reflect-metadata"; const requiredMetadataKey = Symbol("required"); function required(target: Object, propertyKey: string | symbol, parameterIndex: number) { let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || []; existingRequiredParameters.push(parameterIndex); Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey); } function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) { let method = descriptor.value; descriptor.value = function () { let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName); if (requiredParameters) { for (let parameterIndex of requiredParameters) { if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) { throw new Error("Missing required argument."); } } } return method.apply(this, arguments); } }
@required裝飾器新增一個元資料條目,根據需要標記引數。 然後,@validate裝飾器將現有的greet方法包裝在一個函式中,該函式在呼叫原始方法之前驗證引數。
注意: 此示例需要reflect-metadata庫。有關reflect-metadata庫的更多資訊,請參閱元資料。
元資料
一些示例使用reflect-metadata庫,它為實驗元資料API添加了polyfill。 該庫尚未成為ECMAScript(JavaScript)標準的一部分。 但是,一旦裝飾器被正式採用為ECMAScript標準的一部分,這些擴充套件將被提議採用。
您可以通過npm安裝此庫:
npm i reflect-metadata --save
TypeScript包含實驗支援,用於為具有裝飾器的聲明發出某些型別的元資料。 要啟用此實驗性支援,必須在命令列或tsconfig.json中設定emitDecoratorMetadata編譯器選
命令列:
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
tsconfig.json:
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true, "emitDecoratorMetadata": true } }
啟用後,只要匯入了reflect-metadata庫,就會在執行時公開其他設計時型別資訊。
我們可以在以下示例中看到這一點:
import "reflect-metadata"; class Point { x: number; y: number; } class Line { private _p0: Point; private _p1: Point; @validate set p0(value: Point) { this._p0 = value; } get p0() { return this._p0; } @validate set p1(value: Point) { this._p1 = value; } get p1() { return this._p1; } } function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) { let set = descriptor.set; descriptor.set = function (value: T) { let type = Reflect.getMetadata("design:type", target, propertyKey); if (!(value instanceof type)) { throw new TypeError("Invalid type."); } set(value); } }
TypeScript編譯器將使用@ Reflect.metadata裝飾器注入設計時型別資訊。 你可以認為它相當於以下TypeScript:
class Line { private _p0: Point; private _p1: Point; @validate @Reflect.metadata("design:type", Point) set p0(value: Point) { this._p0 = value; } get p0() { return this._p0; } @validate @Reflect.metadata("design:type", Point) set p1(value: Point) { this._p1 = value; } get p1() { return this._p1; } }
注意: 裝飾器元資料是一個實驗性功能,可能會在將來的版本中引入重大更改。