TypeScriptのデコレータとリフレクション:初心者からエキスパートまで(パート1)





翻訳者から:TypeScriptはかなり若く、急速に発展している言語です。 残念ながら、インターネットのロシア語部分には、彼に関するかなりの情報がありますが、それはその人気に寄与していません。



現在ES6に実装されている多くの機能は、TypeScriptのはるか以前に登場しました。 さらに、いくつかの機能と提案されているES7標準も、この言語で実験的に実装されています。 比較的最近登場したそれらの1つ-デコレータ-について説明します。



レモH.ヤンセンが執筆したTypeScriptデコレータに関する記事(または、一連の記事)の翻訳に注目してください







少し前、MicrosoftとGoogle TypeScriptとAngular 2.0のコラボレーション発表しました



TypeScriptとAtScriptを組み合わせ、サイトおよびWebアプリケーションを作成するための人気のあるJavaScriptライブラリの次のバージョンであるAngular 2がTypeScriptで開発されることをお知らせします











注釈とデコレータ





このコラボレーションは、TypeScriptが新しい言語機能を開発するのに役立ちました。その中で、 注釈を強調しています。



アノテーションは、依存性注入またはコンパイラディレクティブで使用するために、クラス宣言にメタデータを追加する方法です。







注釈はGoogleのAtScriptチームによって提案されましたが、標準ではありません。 一方、デコレータは、設計時にクラスとプロパティを変更するためのECMAScript 7の標準として提案されました。



注釈とデコレータは非常に似ています:



注釈とデコレータはほぼ同じです。 ユーザーの観点から見ると、それらはまったく同じ構文を持っています。 違いは、注釈がコードにメタデータを追加する方法を制御しないことです。 デコレータは、注釈のように動作する何かを構築するためのインターフェイスと考えることができます。



ただし、長い目で見れば、デコレータは既存の標準案であるため、デコレータに焦点を当てます。 AtScriptはTypeScriptであり、TypeScriptはデコレーターを実装します。







TypeScriptのデコレータの構文を見てみましょう。



TypeScriptデコレータ





TypeScriptソースコードでは、利用可能なデコレータタイプの署名を見つけることができます:



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: Object, propertyKey: string | symbol, parameterIndex: number) => void;
      
      





ご覧のとおり、 クラスプロパティメソッド、またはパラメーターに注釈を付けるために使用できます 。 これらの各タイプを詳しく見てみましょう。



メソッドデコレータ





デコレータシグネチャがどのように見えるかがわかったので、それらを実装してみましょう。 メソッドdecoratorから始めましょう。 「log」というメソッドデコレータを作成しましょう。



メソッドを宣言する前にデコレータを呼び出すには、シンボル「@」を記述してから、使用するデコレータの名前を記述する必要があります。 デコレータが「ログ」と呼ばれる場合、この構文は次のようになります。



 class C { @log foo(n: number) { return n * 2; } }
      
      





@log



を使用する前に、アプリケーションのどこかでデコレータ自体を宣言する必要があります。 その実装を見てみましょう。



 function log(target: Function, key: string, value: any) { return { value: function (...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(`Call: ${key}(${a}) => ${r}`); return result; } }; }
      
      





メソッドデコレータは3つの引数を取ります。







ちょっと変だよね? C



クラス宣言で@log



デコレータを使用した場合、これらのパラメータは渡しませんでした。 この点に関して、2つの疑問が生じます。これらの議論を誰が通しますか? そして、ログメソッドは正確にどこで呼び出されますか?



それらの答えは、上記の例のためにTypeScriptが生成するコードを見ることで見つけることができます。



  var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; Object.defineProperty(C.prototype, "foo", __decorate([ log ], C.prototype, "foo", Object.getOwnPropertyDescriptor(C.prototype, "foo"))); return C; })();
      
      





@log



デコレータがない場合、クラスC



に対して生成されるJavaScriptコードは次のようになります。



  var C = (function () { function C() { } C.prototype.foo = function (n) { return n * 2; }; return C; })();
      
      





ご覧の@log



追加すると、TypeScriptコンパイラによって次のコードがクラス定義に追加されます。



 Object.defineProperty(C.prototype, "foo", __decorate( [log], // decorators C.prototype, // target "foo", // key Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc ); );
      
      





ドキュメントを参照すると、definePropertyメソッドについて次のことがわかります。



Object.defineProperty()



メソッドは、新しいものを定義するか、オブジェクトの既存のプロパティを直接変更し、このオブジェクトを返します。







TypeScriptコンパイラーは、プロトタイプC



、装飾されたメソッドの名前( 'foo')、および__decorate



関数の結果を__decorate



メソッドに__decorate



ます。



defineProperty



、装飾されたメソッドをオーバーライドするために使用されます。 メソッドの新しい実装は、 __decorate



関数の結果です。 新しい疑問が生じます: __decorate



関数__decorate



どこで__decorate



ますか?




以前にTypeScriptを使用したことがある場合、 extends



を使用すると、コンパイラによって__extends



呼ばれる__extends



生成されることに気付くかもしれません。



 var __extends = this.__extends || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); };
      
      





同様に、デコレータを使用すると、 __decorate



という関数がTypeScriptコンパイラによって生成されます。 彼女を見てみましょう。



 var __decorate = this.__decorate || function (decorators, target, key, desc) { if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { return Reflect.decorate(decorators, target, key, desc); } switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); } };
      
      





このリストの最初の行では、 OR



演算子を使用して、複数回生成された__decorate



関数が__decorate



上書きされないようにします。 2行目では、状態に気付くことができます。



 if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
      
      





この条件は、将来のJavaScript機能( メタデータリフレクションAPI)を検出するために使用されます 。 この一連の記事の最後で詳しく見ていきます。



ちょっと立ち止まって、私たちがこの瞬間に来た経緯を思い出しましょう。 foo



メソッドは、次のパラメーターで呼び出される__decorate



関数の結果によってオーバーライドされます。



 __decorate( [log], // decorators C.prototype, // target "foo", // key Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc );
      
      





これで__decorate



関数の内部になり、メタデータリフレクションAPIが利用できないため、コンパイラによって生成されたバージョンが実行されます



 // arguments.length ===  ,   __decorate() switch (arguments.length) { case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); }
      
      





この場合、4つの引数が__decorate



メソッドに渡されたため、最後のオプションが選択されます。 このコードを扱うのは無意味な変数名のために簡単ではありませんが、私たちは恐れていませんか?



まず、 reduceRightメソッド何をするのかを見てみましょう



reduceRight



は、累積関数を配列の各要素に(右から左の順に)適用し、単一の値を返します。







以下のコードはまったく同じ操作を実行しますが、理解しやすいように書き直されています。



  [log].reduceRight(function(log, desc) { if(log) { return log(C.prototype, "foo", desc); } else { return desc; } }, Object.getOwnPropertyDescriptor(C.prototype, "foo"));
      
      





このコードが実行されると、 log



デコレーターが呼び出され、それに渡されるパラメーターC.prototype



"foo"



およびpreviousValue



ます。 つまり、これで質問に対する答えがわかりました。







foo



後、 foo



メソッドは通常どおり動作し続けますが、その呼び出しはlog



デコレーターに追加された追加のロギング機能もトリガーしlog







 var c = new C(); var r = c.foo(23); // "Call: foo(23) => 46" console.log(r); // 46
      
      







結論





いい冒険だよね? 私と同じように楽しんでいただけたでしょうか。 私たちは始めたばかりで、すでにいくつかの本当にクールなことをする方法を学びました。



メソッドデコレータは、さまざまな興味深い「チップ」に使用できます。 たとえば、 SinonJSのようなテストフレームワーク"" (spy



を使用した場合、 @spy



デコレータを追加するだけで、デコレータを使用してスパイを作成できる可能性があります。



次のパートでは、 プロパティデコレータの使用方法を学習します



All Articles