翻訳者から: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つの引数を取ります。
- target-デコレータが使用されるメソッド。
- keyはこのメソッドの名前です。
- value-このプロパティの記述子(プロパティ記述子)。オブジェクト内に存在する場合、そうでない場合は未定義。 Object.getOwnPropertyDescriptor()メソッドを使用して記述子を取得できます。
ちょっと変だよね?
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
ます。 つまり、これで質問に対する答えがわかりました。
- これらの議論はどこから来たのですか?
-
log
関数はどこで呼び出されますか?
log
デコレータの実装に戻ると、呼び出されたときに何が起こるかをよりよく理解できます。
関数ログ(ターゲット:関数、キー:文字列、値:任意){
// target === C.prototype // key === "foo" // value === Object.getOwnPropertyDescriptor(C.prototype, "foo") return { value: function (...args: any[]) { // , foo, var a = args.map(a => JSON.stringify(a)).join(); // foo() var result = value.value.apply(this, args); // var r = JSON.stringify(result); // console.log(`Call: ${key}(${a}) => ${r}`); // foo return result; } };
}
foo
後、
foo
メソッドは通常どおり動作し続けますが、その呼び出しは
log
デコレーターに追加された追加のロギング機能もトリガーし
log
var c = new C(); var r = c.foo(23); // "Call: foo(23) => 46" console.log(r); // 46
結論
いい冒険だよね? 私と同じように楽しんでいただけたでしょうか。 私たちは始めたばかりで、すでにいくつかの本当にクールなことをする方法を学びました。
メソッドデコレータは、さまざまな興味深い「チップ」に使用できます。 たとえば、 SinonJSのようなテストフレームワークで
"" (spy
を使用した場合、
@spy
デコレータを追加するだけで、デコレータを使用してスパイを作成できる可能性があります。
次のパートでは、 プロパティデコレータの使用方法を学習します 。