TypeScript Handbook を読む (19. Decorators)

[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) を読み進めていく第十九回目。

1. [Basic Types](http://qiita.com/murank/items/9c1f633545842c953947)
1. [Variable Declarations](http://qiita.com/murank/items/913f1c1e4e725047436c)
1. [Interfaces](http://qiita.com/murank/items/553276c3dc2eec9bfe47)
1. [Classes](http://qiita.com/murank/items/7e8f15225bc8259e9bf3)
1. [Functions](http://qiita.com/murank/items/e310ce68ed5a0058d42d)
1. [Generics](http://qiita.com/murank/items/144b686d34fd433a97a3)
1. [Enums](http://qiita.com/murank/items/cc487562b21bae3728ba)
1. [Type Inference](http://qiita.com/murank/items/e67ad7426020321228e9)
1. [Type Compatibility](http://qiita.com/murank/items/e06efb0615294a6826e3)
1. [Advanced Types](http://qiita.com/murank/items/bc1776efbecbd2120d94)
1. [Symbols](http://qiita.com/murank/items/04b3b31caa677750f240)
1. [Iterators and Generators](http://qiita.com/murank/items/7ceec53dde886f31a682)
1. [Modules](http://qiita.com/murank/items/0fc4e059be8ea5e1c274)
1. [Namwspaces](http://qiita.com/murank/items/0dfad4a0bb91aaa6c97b)
1. [Namespaces and Modules](http://qiita.com/murank/items/0be1978a3515fc76e7ba)
1. [Module Resolution](http://qiita.com/murank/items/5f11466474d49498bbd5)
1. [Declaration Merging](http://qiita.com/murank/items/ec90ba04edab1ea3718e)
1. [JSX](http://qiita.com/murank/items/90164aa9a3bed3c49a80)
1. Decorators (今ココ)
1. [Mixins](https://qiita.com/murank/items/e77ad8359ed1fd82822d)
1. [Triple-Slash Directives](https://qiita.com/murank/items/faff57883338ebdeda12)
1. [Type Checking JavaScript Files](https://qiita.com/murank/items/588eaddfe8af59914658)


# Decorators
[原文](https://www.typescriptlang.org/docs/handbook/decorators.html)

## Introduction
TypeScript や ES6 でクラスを使用するにあたって、クラスやクラスメンバにアノテーションを付与することで振る舞いを変更するための機能が必要になる場合もあることでしょう。
デコレータはそのためのメタプログラミング構文を提供します。
デコレータは JavaScript の [stage 2 proposal](https://github.com/tc39/proposal-decorators) であり、TypeScript は試験的機能として提供されています。

デコレータを有効にするためにはコマンドラインまたは `tsconfig.json` で `experimentalDecorators` を有効にする必要があります。

```bash:コマンドライン
tsc --target ES5 --experimentalDecorators
```
```tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}
```


## Decorators
*デコレータ* は [クラス宣言](#class-declaration)、[メソッド](#method)、[アクセサ](#acecssor)、[プロパティ](property)、[引数](parameter) に付与することができる特殊な宣言です。
デコレータは `@expression` という形式で使用します。
この時、`expression` はデコレータが付与されている宣言の情報とともに実行時に関数として呼び出されます。

例えば、`@sealed` というデコレータに対し、以下のような `sealed` 関数を用意します。

```ts:TypeScript
function sealed(target) {
    // 'target' を使用して何かする...
}
```

### Decorator Factories
宣言に対するデコレータの適用方法をカスタマイズしたい場合、デコレータファクトリを作成することができます。
*デコレータファクトリ* は実行時にデコレータから呼び出される式を返すだけの単純な関数です。

デコレータファクトリは以下のように作成します。

```ts:TypeScript
function color(value: string) { // デコレータファクトリ
    return function (target) { // デコレータ
        // 'target' と 'value' を使用して何かする...
    }
}
```

### Decorator Composition
以下のように同一の宣言に対して複数のデコレータを適用することができます。

- 単一行の例:

```ts:TypeScript
@f @g x
```

- 複数行の例:

```ts:TypeScript
@f
@g
x
```

同一の宣言に対して複数のデコレータが適用されている場合、次の順で評価が行われます。

1. 各デコレータの式が上から下へ評価される
2. 上記の評価結果が関数として下から上へ評価される

[デコレータファクトリ](#decorator-factories) を使用すると、この評価順がよく分かります。

```ts:TypeScript
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}
```

上記のコードを実行すると以下のように出力されます。

```
f(): evaluated
g(): evaluated
g(): called
f(): called
```


### Decorator Evaluation
クラス内の各宣言に適用されたデコレータは以下の順で評価されます。

1. インスタンスメンバに対する *引数デコレータ*、*メソッドデコレータ*、*アクセサデコレータ*、*プロパティデコレータ*
2. 静的メンバに対する *引数デコレータ*、*メソッドデコレータ*、*アクセサデコレータ*、*プロパティデコレータ*
3. コンストラクタに対する *引数デコレータ*
4. クラスに対する *クラスデコレータ*


### Class Decorators
*クラスデコレータ* とはクラス宣言の直前に宣言するデコレータであり、コンストラクタに適用されることでクラス定義を監視、変更、置換することができます。
クラスデコレータは宣言ファイル、つまり (`declare` クラスといった) アンビエントコンテキスト内で使用することはできません。

クラスデコレータは実行時に関数として評価されますが、その引数として修飾されているコンストラクタを受け取ります。

もしクラスデコレータで新しいコンストラクタ関数を返却する場合、元の prototype を維持するように注意する必要があります。
なぜなら、デコレータに対してこの処理は **行われない** ためです。

`Greeter` クラスに対するクラスデコレータ (`@sealed`) の適用例は以下の通りです。

```ts:TypeScript
@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
```
```js:JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());
Greeter = __decorate([
    sealed
], Greeter);
```
>デコレータ経由でインスタンス化されるように、元のクラス宣言をラッピングするわけだ。

そして `@sealed` を以下のように定義したとします。

```ts:TypeScript
function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}
```

これにより、`@sealed` が実行されるとコンストラクタと prototype が変更不可になります。

次にコンストラクタをオーバーライドする例を見てみましょう。

```ts:TypeScript
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world")); // Object { property: "property", hello: "override", newProperty: "new property" }
```
```js:JavaScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

function classDecorator(constructor) {
    return (function (_super) {
        __extends(class_1, _super);
        function class_1() {
            var _this = _super !== null && _super.apply(this, arguments) || this;
            _this.newProperty = "new property";
            _this.hello = "override";
            return _this;
        }
        return class_1;
    }(constructor));
}

var Greeter = (function () {
    function Greeter(m) {
        this.property = "property";
        this.hello = m;
    }
    return Greeter;
}());
Greeter = __decorate([
    classDecorator
], Greeter);

console.log(new Greeter("world")); // Object { property: "property", hello: "override", newProperty: "new property" }
```
>`Greeter` のコンストラクタが呼ばれた後に `hello = "override"` が実行されるため、結果として `hello` プロパティは `"override"` になるわけだ。


### Method Decorators
*メソッドデコレータ* とはメソッド宣言の直前に宣言するデコレータであり、*プロパティディスクリプタ* に適用されることでメソッド定義を監視、変更、置換することができます。
メソッドデコレータはオーバーロードに対してや、宣言ファイル、つまり (`declare` クラスといった) アンビエントコンテキスト内で使用することはできません。

メソッドデコレータは実行時に関数として評価されますが、その引数として次の 3 つの引数を受け取ります。

1. コンストラクタ関数 (静的メンバの場合) またはクラスの prototype (インスタンスメンバの場合)
2. メンバの名前
3. メンバの *プロパティディスクリプタ*  
   (ただし、`ES5` 未満をターゲットとしている場合、*プロパティディスクリプタ* は `undefined` になります)

メソッドデコレータが値を返却した場合、メソッドに対するプロパティディスクリプタとして使用されます。
ただし、`ES5` 未満をターゲットとしている場合、返却値は無視されます。

`Greeter` クラスのメソッドにメソッドデコレータ (`@enumerable`) を適用する例は以下の通りです。

```ts:TypeScript
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}
```
```js:JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());
__decorate([
    enumerable(false)
], Greeter.prototype, "greet", null);
```

そして `@enumerable` を以下のように定義したとします。

```ts:TypeScript
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
```

`@enumerable(false)` は [デコレータファクトリ](#decorator-factories) であり、`@enumerable(false)` が実行されるとプロパティディスクリプタの `enumerable` プロパティが変更されます。


### Accessor Decorators
*アクセサデコレータ* とはアクセサ宣言の直前に宣言するデコレータであり、*プロパティディスクリプタ* に適用されることでアクセサの定義を監視、変更、置換することができます。
アクセサデコレータは宣言ファイル、つまり (`declare` クラスといった) アンビエントコンテキスト内で使用することはできません。

注: TypeScript では単一のメンバに対して `get` アクセサと `set` アクセサの両方を修飾することを許可していません。
その代わりに、メンバの全デコレータはソースの記載順で最初のアクセサに適用されます。
これが個々のアクセサではなく、*プロパティディスクリプタ* (`get` アクセサと `set` アクセサの組) に対してデコレータが適用される理由です。

アクセサデコレータは実行時に関数として評価されますが、その引数として次の 3 つの引数を受け取ります。

1. コンストラクタ関数 (静的メンバの場合) またはクラスの prototype (インスタンスメンバの場合)
2. メンバの名前
3. メンバの *プロパティディスクリプタ*  
   (ただし、`ES5` 未満をターゲットとしている場合、*プロパティディスクリプタ* は `undefined` になります)

アクセサデコレータが値を返却した場合、メンバに対する *プロパティディスクリプタ* として使用されます。
ただし、`ES5` 未満をターゲットとしている場合、返却値は無視されます。

`Greeter` クラスのメソッドにメソッドデコレータ (`@enumerable`) を適用する例は以下の通りです。

```ts:TypeScript
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; }
}
```
```js:JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Point = (function () {
    function Point(x, y) {
        this._x = x;
        this._y = y;
    }
    Object.defineProperty(Point.prototype, "x", {
        get: function () { return this._x; },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Point.prototype, "y", {
        get: function () { return this._y; },
        enumerable: true,
        configurable: true
    });
    return Point;
}());
__decorate([
    configurable(false)
], Point.prototype, "x", null);
__decorate([
    configurable(false)
], Point.prototype, "y", null);
```

`@configurable` は以下のように定義することができます。

```ts:TypeScript
function configurable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}
```

### Property Decorators
*プロパティデコレータ* とはプロパティ宣言の直前に宣言するデコレータです。
プロパティデコレータは宣言ファイル、つまり (`declare` クラスといった) アンビエントコンテキスト内で使用することはできません。

プロパティデコレータは実行時に関数として評価されますが、その引数として次の 2 つの引数を受け取ります。

1. コンストラクタ関数 (静的メンバの場合) またはクラスの prototype (インスタンスメンバの場合)
2. メンバの名前

*プロパティディスクリプタ* は引数として渡されない点に注意してください
これは、現時点では prototype のメンバを定義する際にインスタンスプロパティを宣言するための方法がなく、プロパティの監視や変更を行うことができないためです。
また、プロパティデコレータの戻り値も無視されます。
そのため、プロパティデコレータは指定された名前のプロパティがクラス内に宣言されていることを監視するためにしか使用することはできません。

以下のように、引数で渡された情報を使用してプロパティに関するメタデータを記録しておくことが可能です。

```ts: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);
    }
}
```
```js:JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        var formatString = getFormat(this, "greeting");
        return formatString.replace("%s", this.greeting);
    };
    return Greeter;
}());
__decorate([
    format("Hello, %s")
], Greeter.prototype, "greeting", void 0);
```

`@format` デコレータと `getFormat` 関数は以下のように定義することができます。

```ts:TypeScript
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")` デコレータは [デコレータファクトリ](#decorator-factories) です。
`@format("Hello, %s")` が呼ばれると `reflect-metadata` ライブラリの `Reflect.metadata` 関数を使用してプロパティのメタデータを記録し、`getFormat` が呼ばれた時にそのメタデータを読み出します。


### Parameter Decorators
*パラメータデコレータ* とは引数宣言の直前に宣言するデコレータであり、クラスコンストラクタやメソッドに適用されます。
パラメータデコレータは宣言ファイル、つまり (`declare` クラスといった) アンビエントコンテキスト内で使用することはできません。

パラメータデコレータは実行時に関数として評価されますが、その引数として次の 3 つの引数を受け取ります。

1. コンストラクタ関数 (静的メンバの場合) またはクラスの prototype (インスタンスメンバの場合)
2. メンバの名前
3. 関数の引数リスト内での序数

パラメータデコレータはメソッドにパラメータが宣言されていることを監視するためだけに使用できる点に注意してください。
 
パラメータデコレータの戻り値は無視されます。

`Greeter` クラスのメソッドの引数にパラメータデコレータ (`@required`) を適用する例は以下の通りです。

```ts:TypeScript
class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @validate
    greet(@required name: string) {
        return "Hello " + name + ", " + this.greeting;
    }
}
```
```js:JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function (name) {
        return "Hello " + name + ", " + this.greeting;
    };
    return Greeter;
}());
__decorate([
    validate,
    __param(0, required)
], Greeter.prototype, "greet", null);
```

`@required` デコレータと `@validate` デコレータは以下のように定義することができます。

```ts:TypeScript
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` メソッドをラップし、元の関数を呼び出す前にバリデーションを行います。


### Metadata
いくつかの例で [試験的なメタデータAPI](https://github.com/rbuckton/ReflectDecorators) の polyfill である `reflect-metadata` ライブラリを使用しています。
このライブラリはまだ ECMAScript (JavaScript) 標準の一部ではありませんが、デコレータが　ECMAScript 標準に採用されれば、これらの拡張機能も採用を提案されることでしょう。

このライブラリは npm を使って以下のようにインストールすることができます。

```
npm i reflect-metadata --save
```

TypeScript ではデコレータが指定された宣言に対して、特定の型のメタデータを出力する機能を試験的にサポートしています。
この機能を有効にするには `emitDecoratorMetadata` コンパイラオプションをコマンドラインまたは `tsconfig.json` に指定する必要があります。

```bash:コマンドライン
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
```

```bash:tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}
```

この機能を有効にし、`reflect-metadata` ライブラリをインポートすると、追加のデザイン時型情報が実行時にも参照できるようになります。

具体例は以下の通りです。

```ts:TypeScript
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 コードと等価と言えます。

```ts: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; }
}
```

デコレータメタデータは試験的な機能であり、将来のリリースでは快適な変更が行われる可能性がある点に注意してください。