
この記事はシリーズの第3部です。
前回、デコレータとは何か、そしてそれらがTypeScriptでどのように実装されているかを学びました。 クラス、プロパティ、およびメソッドデコレータを操作する方法を知っています。
この記事では、以下について説明します。
- 最後に残ったデコレーターのタイプはパラメーターデコレーターです。
- デコレータファクトリの実装
- 構成可能なデコレータの実装
これらの概念を示すために、次のクラスを使用します。
class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
パラメータデコレータ
既に知っているように、パラメータデコレータのシグネチャは次のとおりです。
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
logParameter
というデコレーターを使用すると、 logParameter
ようになります。
class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
JavaScriptでコンパイルすると、ここで__decorate
メソッドが呼び出されます( 最初の部分で説明しました )。
Object.defineProperty(Person.prototype, "saySomething", __decorate([ __param(0, logParameter) ], Person.prototype, "saySomething", Object.getOwnPropertyDescriptor(Person.prototype, "saySomething"))); return Person;
前のタイプのデコレーターとの類推により、 Object.defineProperty
メソッドがObject.defineProperty
と、 saySomething
メソッドは__decorate
関数を呼び出した結果(メソッドデコレーターのように)に置き換えられると想定できます。 この仮定は誤りです。
上記のコードをよく見ると、そこに新しい__param
関数__param
あることに気付くでしょう。 TypeScriptコンパイラーによって生成され、次のようになります。
var __param = this.__param || function (index, decorator) { // return a decorator function (wrapper) return function (target, key) { // apply decorator (return is ignored) decorator(target, key, index); } };
__param
関数は、入力に渡されたdecorator
(名前がdecorator
)をラップするデコレータを返します。
パラメータデコレータが呼び出されると、その値が無視されることに気付くかもしれません。 つまり、 __decorate
関数を呼び出しても、その実行結果はsaySomething
メソッドをオーバーライドしません。
したがって、 パラメーターデコレーターは何も返しません 。
__param
デコレータの__param
、クロージャ内のインデックス(引数リストでデコレートされるパラメータの位置)を保存するために使用されます。
class foo { // foo index === 0 public foo(@logParameter foo: string) : string { return "bar"; } // bar index === 1 public foobar(foo: string, @logParameter bar: string) : string { return "foobar"; } }
これで、パラメーターデコレータが3つの引数を取ることがわかりました。
- 装飾されたクラスのプロトタイプ
- 装飾するパラメーターを含むメソッドの名前
- 装飾されたパラメーターのインデックス
logProperty
実装しましょう
function logParameter(target: any, key : string, index : number) { var metadataKey = `log_${key}_parameters`; if (Array.isArray(target[metadataKey])) { target[metadataKey].push(index); } else { target[metadataKey] = [index]; } }
上記のパラメーターデコレータは、クラスプロトタイプに新しいプロパティ( metadataKey
)を追加します。 このプロパティは、装飾するパラメーターのインデックスを含む配列です。 このプロパティメタデータを検討できます。
パラメーターデコレーターは、コンストラクター、メソッド、またはプロパティの動作を変更するために使用されないことを前提としています。 パラメータデコレータは、さまざまなメタデータを作成するためにのみ使用してください 。
メタデータが作成されたら、別のデコレータを使用してそれを読み取ることができます。 たとえば、以下は記事の第2部のメソッドデコレータの修正版です。
元のバージョンは、呼び出されたときにメソッドの名前とそのすべての引数をコンソールに表示しました。 新しいバージョンはmetadataを読み取り 、それに基づいて、対応するパラメーターデコレータでマークされた引数のみを表示します。
class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } } function logMethod(target: Function, key: string, descriptor: any) { var originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { var metadataKey = `__log_${key}_parameters`; var indices = target[metadataKey]; if (Array.isArray(indices)) { for (var i = 0; i < args.length; i++) { if (indices.indexOf(i) !== -1) { var arg = args[i]; var argStr = JSON.stringify(arg) || arg.toString(); console.log(`${key} arg[${i}]: ${argStr}`); } } var result = originalMethod.apply(this, args); return result; } else { var a = args.map(a => (JSON.stringify(a) || a.toString())).join(); var result = originalMethod.apply(this, args); var r = JSON.stringify(result); console.log(`Call: ${key}(${a}) => ${r}`); return result; } } return descriptor; }
次のパートでは、メタデータを操作する最良の方法であるメタデータリフレクションAPIについて学習します 。 以下に、学習内容の小さな例を示します。
function logParameter(target: any, key: string, index: number) { var indices = Reflect.getMetadata(`log_${key}_parameters`, target, key) || []; indices.push(index); Reflect.defineMetadata(`log_${key}_parameters`, indices, target, key); }
デコレータ工場
TypeScriptのデコレータの公式提案では、デコレータファクトリについて次の定義を提供しています。
デコレータファクトリは、任意の数の引数を取ることができ、いずれかのタイプのデコレータを返す関数です。
すべての種類のデコレータ(クラス、メソッド、プロパティ、およびパラメータ)を実装および使用する方法を既に学習しましたが、何かを改善することができます。 次のようなコードがあるとします。
@logClass class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
期待通りに動作しますが、この例のように、タイプを気にせずにどこでも同じデコレータを使用できると良いでしょう:
@log class Person { @log public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @log public saySomething(@log something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
これを実現するには、ファクトリーでデコレーターをラップします。 ファクトリーは、渡された引数に必要なデコレーターのタイプを判別できます。
function log(...args : any[]) { switch(args.length) { case 1: return logClass.apply(this, args); case 2: return logProperty.apply(this, args); case 3: if(typeof args[2] === "number") { return logParameter.apply(this, args); } return logMethod.apply(this, args); default: throw new Error("Decorators are not valid here!"); } }
設定可能なデコレータ
この記事で説明したい最後のポイントは、デコレータを使用するときにデコレータに引数を渡す方法です。
@logClassWithArgs({ when : { name : "remo"} }) class Person { public name: string;
デコレータファクトリを使用して、構成可能なデコレータを作成できます。
function logClassWithArgs(filter: Object) { return (target: Object) => {
同じアイデアを他のタイプのデコレーターにも適用できます。
おわりに
これで、既存の4種類のデコレータすべて、デコレータファクトリの作成方法、およびそれらをパラメータ化する方法を深く理解できました。
次の記事では、 Metadata Reflection APIの使用方法を学習します 。