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

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

Source: https://habr.com/ru/post/J275003/


All Articles