1. 程式人生 > 實用技巧 >TypeScript 裝飾器

TypeScript 裝飾器

1..裝飾器的概念

  · 裝飾器是一種特殊型別的宣告,它能夠被附加到類的宣告,方法,訪問符,屬性或引數上;

  · 裝飾器使用 @expression 這種形式,expression求值後必須為一個函式;

  · 它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入。

  · 裝飾器執行時機是在程式碼編譯時發生的(不是 TypeScript 編譯,而是在 JavaScript 執行編譯階段)

啟用裝飾器特性,必須在命令列或 tsconfig.json 裡啟用 experimentalDecorators 編譯器選項:

2.裝飾器語法

啟用裝飾器特性,必須在命令列或 tsconfig.json 裡啟用 experimentalDecorators 編譯器選項:

// 在命令列中使用
tsc--target ES5--experimentalDecorators

// tsconfig.json 中開啟:
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

裝飾器定義與應用舉例:

// 裝飾器定義
function sealed(target) {
    // do something with "target" ...
}
// 或使用裝飾器工廠函式
function color(value: string) { // 這是一個裝飾器工廠
    return function (target) { // 這是裝飾器
        // do something with "target" and "value"...
    }
}

// 應用到一個宣告上,例如:
@sealed @color let x: string;
// 或
@sealed
@color
let x: string

在 TypeScript 裡,當多個裝飾器應用在一個宣告上時會進行如下步驟的操作:

  · 由上至下依次對裝飾器表示式求值。

  · 求值的結果會被當作函式,由下至上依次呼叫。

多個裝飾器應用在一個宣告,舉例:

// 多個裝飾器應用在一個宣告
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string) {
        console.log("f(): called");
    }
}
function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string) {
        console.log("g(): called");
    }
}
class C {
    @f() @g() method() { }
}
// f(): evaluated
// g(): evaluated
// g(): called
// f(): called

3.類裝飾器

類中不同宣告上的裝飾器將按以下規定的順序應用:

  · 有多個引數裝飾器時:從最後一個引數依次向前執行。

  · 方法和方法引數中,引數裝飾器先執行。

  · 方法和屬性裝飾器,誰在前面誰先執行。因為引數屬於方法一部分,所以引數會挨著方法執行。

  ·類裝飾器總是最後執行。

類裝飾器在類宣告之前被宣告(緊靠著類宣告)。

類裝飾器應用於類建構函式,可以用來監視,修改或替換類定義。

// 類裝飾符例項
function Path(path: string) {
    return function (target: Function) {
        !target.prototype.$Meta && (target.prototype.$Meta = {})
        target.prototype.$Meta.baseUrl = path;
    };
}

@Path('/hello')
class HelloService {
    [x: string]: any;
    constructor() { }
}

console.log(HelloService.prototype.$Meta);  // => { baseUrl: '/hello' }
let hello = new HelloService();
console.log(hello.$Meta)                    // => { baseUrl: '/hello' }

4.方法裝飾器

方法裝飾器宣告在一個方法的宣告之前,可以用來監視,修改或者替換方法定義。

方法裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:

  · 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;

  · 成員的名字;

  · 成員的屬性描述符;

  注意:如果程式碼輸出目標版本小於ES5,屬性描述符將會是 undefined,返回值會被忽略。 如果方法裝飾器返回一個值,它會被用作方法的屬性描述符。

方法裝飾器,舉例:

// 方法裝飾符例項
function GET(alias: string) {
    return function (target, methodName: string, descriptor: PropertyDescriptor) {
        target._alias = alias;
    }
}

class HelloService {
    _alias: string;
    constructor() { }
    @GET("getMyName")
    getUser() { }
}
let hello = new HelloService();
console.log(hello._alias);  // => gtMyName

5.訪問裝飾器

訪問器裝飾器宣告在一個訪問器的宣告之前,可以用來監視,修改或替換一個訪問器的定義。

訪問裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:

  · 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;

  · 成員的名字;

  · 成員的屬性描述符;

  注意:如果程式碼輸出目標版本小於ES5,屬性描述符將會是 undefined,返回值會被忽略。

    如果訪問裝飾器返回一個值,它會被用作方法的屬性描述符。

  TypeScript 不允許同時裝飾一個成員的get和set訪問器;

  一個成員的所有裝飾的必須應用在文件順序的第一個訪問器上;因為在裝飾器應用於一個屬性描述 符時,它聯 合了get和set訪問器,而不是分開宣告的。

訪問裝飾器,舉例:

// 訪問裝飾器例項
function access(value: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        !target.$Meta && (target.$Meta = {})
        target.$Meta[propertyKey] = `It's a ${value} method`
    };
}
class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }
    @access('get')
    get x() { return this._x; }
    @access('set')
    set y(y: number) { this._y = y; }
}
let point = new Point(1, 2);
console.log(point['$Meta']);    // => { x: "It's a get method", y: "It's a set method" }

6..屬性裝飾器

屬性裝飾器宣告在一個屬性宣告之前。

屬性裝飾器表示式會在執行時當作函式被呼叫,傳入下列2個引數:

  · 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件。

  · 成員的名字。

  注意: 屬性描述符不會做為引數傳入屬性裝飾器,因為目前無法在定義一個原型物件的成員時描述一個例項屬性,且無法監視或修改一個屬性的初始化方法,因此,屬性描述符只能用來監視類中是否聲明瞭某個名字的屬性。

屬性裝飾器,舉例:

// 屬性裝飾符例項
function DefaultValue(value: string) {
    return function (target: any, propertyName: string) {
        target[propertyName] = value;
    }
}
class Hello {
    @DefaultValue("world")
    greeting: string;
}
console.log(new Hello().greeting);  // => world

7.引數裝飾器

引數裝飾器宣告在一個引數宣告之前, 引數裝飾器應用於類建構函式或方法宣告。

引數裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:

  · 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;

  · 成員的名字;

  · 引數在函式引數列表中的索引;

  注意:

    引數裝飾器只能用來監視一個方法的引數是否被傳入。

    引數裝飾器的返回值會被忽略。

引數裝飾器,舉例:

// 引數裝飾器例項
function PathParam(paramName: string) {
    return function (target, methodName: string, paramIndex: number) {
        !target.$Meta && (target.$Meta = {});
        target.$Meta[paramIndex] = paramName;
    }
}
class HelloService {
    constructor() { }
    getUser(
        @PathParam("the user's id")
        userId: string
    ) { }
}
console.log((<any>HelloService).prototype.$Meta);   // => { '0': "the user's id" }