TypeScript基礎入門之裝飾器(三)

轉載 TypeScript基礎入門之裝飾器(三)git

繼續上篇文章[TypeScript基礎入門之裝飾器(二)]github

訪問器裝飾器

Accessor Decorator在訪問器聲明以前聲明。 訪問器裝飾器應用於訪問器的屬性描述符,可用於觀察,修改或替換訪問者的定義。 訪問器裝飾器不能在聲明文件中使用,也不能在任何其餘環境上下文中使用(例如在聲明類中)。npm

注意: TypeScript不容許爲單個成員裝飾get和set訪問器。相反,該成員的全部裝飾器必須應用於按文檔順序指定的第一個訪問器。這是由於裝飾器適用於屬性描述符,它結合了get和set訪問器,而不是單獨的每一個聲明。json

訪問器裝飾器的表達式將在運行時做爲函數調用,具備如下三個參數:app

  1. 靜態成員的類的構造函數,或實例成員的類的原型。
  2. 成員的名字。
  3. 會員的財產描述。

注意: 若是腳本目標小於ES5,則屬性描述符將不肯定。函數

若是訪問器裝飾器返回一個值,它將用做該成員的屬性描述符。ui

注意: 若是腳本目標小於ES5,則忽略返回值。this

如下是應用於Point類成員的訪問器裝飾器(@configurable)的示例:spa

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在屬性聲明以前聲明。 屬性修飾器不能在聲明文件中使用,也不能在任何其餘環境上下文中使用(例如在聲明類中)。

屬性裝飾器的表達式將在運行時做爲函數調用,具備如下兩個參數:

  1. 靜態成員的類的構造函數,或實例成員的類的原型。
  2. 成員的名字。

注意: 因爲在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庫的更多信息,請參閱元數據。

參數裝飾器

參數裝飾器在參數聲明以前聲明。 參數裝飾器應用於類構造函數或方法聲明的函數。 參數裝飾器不能用於聲明文件,重載或任何其餘環境上下文(例如聲明類中)。

參數裝飾器的表達式將在運行時做爲函數調用,具備如下三個參數:

  1. 靜態成員的類的構造函數,或實例成員的類的原型。
  2. 成員的名字。
  3. 函數參數列表中參數的序數索引。

注意: 參數裝飾器只能用於觀察已在方法上聲明參數。

將忽略參數裝飾器的返回值。

如下是應用於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; }
}

注意: 裝飾器元數據是一個實驗性功能,可能會在未來的版本中引入重大更改。

相關文章
相關標籤/搜索