web-dev-qa-db-ja.com

TypeScriptデコレータを実装する方法

TypeScript 1.5デコレータ になりました。

誰かがデコレータを実装する適切な方法を示す簡単な例を提供し、可能な有効なデコレータシグネチャの引数が何を意味するのか説明してもらえますか?

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Function, propertyKey: string | symbol, parameterIndex: number) => void;

また、デコレータを実装する際に留意すべきベストプラクティスの考慮事項はありますか?

191
David Sherret

私はデコレータで遊んでしまったので、ドキュメントが出る前にこれを利用したい人のために私が考え出したものをドキュメントにすることにしました。間違いがある場合は、自由に編集してください。

一般的なポイント

  • オブジェクトがインスタンス化されるときではなく、クラスが宣言されるときにデコレータが呼び出されます。
  • 同じClass/Property/Method/Parameterに複数のデコレータを定義することができます。
  • デコレータはコンストラクタには使用できません。

有効なデコレータは次のとおりです。

  1. デコレータタイプの1つ(ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator)に割り当て可能です。
  2. 装飾された値に代入可能な値(クラスデコレータとメソッドデコレータの場合)を返します。

参考


メソッド/フォーマルアクセサデコレータ

実装パラメータ

  • target:クラスのプロトタイプ(Object).
  • propertyKey:メソッドの名前(string | symbol )。
  • descriptor:A TypedPropertyDescriptor - ディスクリプタのキーに慣れていない場合は、Object.definePropertyこのドキュメント で読むことをお勧めします(3番目のパラメータです)。

例 - 引数なし

つかいます:

class MyClass {
    @log
    myMethod(arg: string) { 
        return "Message -- " + arg;
    }
}

実装:

function log(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
    const originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in 
    // order to use the correct value of `this` in this method (see notes below)
    descriptor.value = function(...args: any[]) {
        // pre
        console.log("The method args are: " + JSON.stringify(args));
        // run and store result
        const result = originalMethod.apply(this, args);
        // post
        console.log("The return value is: " + result);
        // return the result of the original method (or modify it before returning)
        return result;
    };

    return descriptor;
}

入力:

new MyClass().myMethod("testing");

出力:

メソッドの引数は次のとおりです。["testing"]

戻り値は以下のとおりです。Message - testing

ノート:

  • 記述子の値を設定するときに矢印の構文を使用しないでください。 thisのコンテキストは、インスタンスのインスタンスにはなりません
  • 新しいディスクリプタを返すことによって現在のディスクリプタを上書きするよりも、オリジナルのディスクリプタを変更する方が得策です。これにより、他のデコレータが行ったことを上書きせずにディスクリプタを編集する複数のデコレータを使用できます。こうすることで@enumerable(false)@logのようなものを同時に使うことができます(例: 悪い vs 良い
  • 便利なTypedPropertyDescriptorのtype引数は、どのメソッドシグネチャ( メソッド例 )またはアクセサシグネチャ( - )を制限するために使用できます。 アクセサ例 )デコレータをつけることができます。

例題 - 引数付き(デコレータファクトリ)

引数を使用するときは、デコレータのパラメータで関数を宣言してから、引数なしで例のシグネチャを持つ関数を返す必要があります。

class MyClass {
    @enumerable(false)
    get prop() {
        return true;
    }
}

function enumerable(isEnumerable: boolean) {
    return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
        descriptor.enumerable = isEnumerable;
        return descriptor;
    };
}

静的メソッドデコレータ

いくつか違いがあるメソッドデコレータに似ています:

  • そのtargetパラメータはコンストラクタ関数そのものであり、プロトタイプではありません。
  • 記述子はプロトタイプではなくコンストラクタ関数で定義されます。

クラスデコレータ

@isTestable
class MyClass {}

実装パラメータ:

  • target:デコレータが宣言されているクラス(TFunction extends Function)。

使用例 :クラスに関する情報を格納するためのメタデータAPIの使用。


プロパティデコレータ

class MyClass {
    @serialize
    name: string;
}

実装パラメータ

  • target:クラスのプロトタイプ(Object).
  • propertyKey:プロパティーの名前(string | symbol )。

使用例@serialize("serializedName")デコレータを作成し、シリアル化するプロパティのリストにプロパティ名を追加します。


パラメータデコレータ

class MyClass {
    myMethod(@myDecorator myParameter: string) {}
}

実装パラメータ

  • target:クラスのプロトタイプ(Function - Functionはもう動作しないようです。任意のクラス内でデコレータを使用するには、ここでanyまたはObjectを使用する必要があります。または制限するクラスタイプを指定します)に)
  • propertyKey:メソッドの名前(string | symbol )。
  • parameterIndex:関数のパラメーターのリストにあるパラメーターのインデックス(number)。

簡単な例

詳細な例

372
David Sherret

他の答えでは見られない重要なことが1つあります。

デコレータファクトリー

デコレータを宣言に適用する方法をカスタマイズしたい場合は、デコレータファクトリを書くことができます。デコレータファクトリは、実行時にデコレータによって呼び出される式を返す関数です。

// This is a factory, returns one of ClassDecorator,
// PropertyDecorator, MethodDecorator, ParameterDecorator
function Entity(discriminator: string):  {
    return function(target) {
        // this is the decorator, in this case ClassDecorator.
    }
}

@Entity("cust")
export class MyCustomer { ... }

TypeScriptハンドブック デコレータの章 を確認してください。

8
Ondra Žižka
class Foo {
  @consoleLogger 
  Boo(name:string) { return "Hello, " + name }
}
  • target:上記の場合のクラスのプロトタイプは "Foo"です。
  • propertyKey:呼び出されたメソッドの名前、上記の場合は "Boo"
  • 記述子:object =>の記述はvalueプロパティを含み、それは関数そのものです:function(name){return 'Hello' + name; }

コンソールへの各呼び出しをログに記録するようなものを実装することができます。

function consoleLogger(target: Function, key:string, value:any) 
{
  return value: (...args: any[]) => 
  {
     var a = args.map(a => JSON.stringify(a)).join();
     var result = value.value.apply(this, args);
     var r = JSON.stringify(result);

     console.log('called method' + key + ' with args ' + a + ' returned result ' + r);

     return result;
  }     
}
4
Erik Lieben