デコレータは、Angular 2+での使用により人気を博しています。 Angularでは、この機能はTypeScriptを使用して実装されます。 JavaScriptでデコレータを導入するための提案は、 ステージ2ドラフト状態です。 つまり、それらの作業は基本的に完了していますが、変更される可能性があります。 デコレータは、次の言語アップデートの一部である必要があります。
デコレータとは何ですか?
最も単純な形式では、デコレータは、あるコードを別のコードにラップする方法です。 文字通り-コードの「装飾」。
この概念を「機能構成」または「高次の機能」として以前に聞いたことがあるかもしれません。
これは、標準のJavaScriptツールによって完全に実装されます。 別の関数をラップする関数の呼び出しのように見えます:
function doSomething(name) { console.log('Hello, ' + name); }
function loggingDecorator(wrapped) { return function() { console.log('Starting'); const result = wrapped.apply(this, arguments); console.log('Finished'); return result; } }
const wrapped = loggingDecorator(doSomething);
この例は、
wrapped
された定数に割り当てられる新しい関数を作成する方法を示しています。 この関数は、
doSomething
関数と同様に呼び出すことができ、同じことを行います。 違いは、関数の呼び出しの前後で、ロギングが実行されるという事実にあります。
doSomething
と
wrapped
関数を試してみるとどうなるでしょう。
doSomething('Graham'); // Hello, Graham wrapped('Graham'); // Starting // Hello, Graham // Finished
JavaScriptでデコレータを適用する方法は?
JavaScriptのデコレータは、特別な構文を使用します。
@
記号の形式のプレフィックスがあり、装飾するコードの直前に配置されます。
1つのコードに必要な数のデコレータを適用できます。デコレータは、コード内で従う順序に関与します。
例:
@log() @immutable() class Example { @time('demo') doSomething() { } }
これがクラス宣言と3つのデコレータの使用です。 それらのうちの2つはクラス自体に関連し、1つはクラスプロパティに関連しています。 これらのデコレータの役割は次のとおりです。
-
@log
は、クラスへのすべての呼び出しを@log
でき@log
。
-
@immutable
はクラスを不変にすることができます-クラスの新しいインスタンスに対してObject.freeze
を呼び出す可能性があります。
-
@time
は、メソッドの実行時間に関する情報を記録し、この情報を一意のタグとともにログに表示します。
現在、ブラウザもNode.jsもデコレータをサポートしていないため、デコレータを使用するにはトランスパイラを使用する必要があります。
Babelを使用する場合、 transform-decorators-legacyプラグインを使用してデコレーターを操作できます。
このプラグインの名前には「レガシー」という名前が使用されていることに注意してください。これは、時代遅れのテクノロジーを示すものと解釈できます。 ここでのポイントは、デコレータがBabel 5を処理する方法をサポートしていることです。このアプローチは、最終的に標準化される形式とは異なる場合があります。
なぜデコレータが必要なのですか?
JavaScriptの機能構成は、標準ツールを使用して簡単に実装できます。 ただし、同じアプローチを他のソフトウェア構成、たとえばクラスとそのプロパティに適用することは非常に困難または不可能です。 提案された革新により、クラスとそのプロパティの両方でデコレータを使用できます。 おそらくJavaScriptの将来のバージョンでは、デコレータのさらなる開発が期待できます。
他の機能の中でも特にデコレータの使用は、より明確な構文を意味します。これは、この手法を使用するプログラマーの意図をより明確に表現することにつながります。
さまざまな種類のデコレータ
現在サポートされているデコレーターのタイプは、クラスおよびクラスメンバーのデコレーター(プロパティ、メソッド、ゲッター、セッター)です。 実際、デコレーターは、装飾される要素に関するいくつかの情報とともに呼び出され、他の関数を返す関数です。
デコレータはプログラムの起動時に一度実行され、装飾されたコードは戻り値に置き換えられます。
▍クラスメンバーデコレータ
クラスメンバーデコレータは、プロパティ、メソッド、ゲッター、セッターに適用されます。
これらのデコレーター関数は、3つのパラメーターで呼び出されます。
-
target —
クラスの装飾されたメンバーが配置されているクラス。 -
name —
クラスメンバーの名前です。
-
descriptor —
クラスのメンバーへdescriptor —
ハンドルです。 これは基本的に、 Object.definePropertyメソッドに渡されるオブジェクトです。
@readonly
デコレータの使用方法を示す古典的な例を次に示します。 このデコレータは次のように実装されます。
function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }
デコレータは、
writable
プロパティ記述子フラグを
false
設定し
false
。
次に、このデコレータをクラスメンバーと共に次のように使用します。
class Example { a() {} @readonly b() {} }
const e = new Example(); ea = 1; eb = 2; // TypeError: Cannot assign to read only property 'b' of object '#<Example>'
エラーメッセージからわかるように、デコレータは期待どおりに機能しました。 たとえば、装飾されている関数の動作を変更し、実際には新しい関数に置き換えて、さらに先へ進むことができます。 デコレータを使用して、関数の引数とその戻り値を記録しましょう。
function log(target, name, descriptor) { const original = descriptor.value; if (typeof original === 'function') { descriptor.value = function(...args) { console.log(`Arguments: ${args}`); try { const result = original.apply(this, args); console.log(`Result: ${result}`); return result; } catch (e) { console.log(`Error: ${e}`); throw e; } } } return descriptor; }
このコンストラクトは、メソッドを新しいものに置き換えます。新しいメソッドは、引数を記録し、元のメソッドを呼び出し、返されたものを記録します。
ここでは、メソッドに渡されるすべての引数を持つ配列を自動的に作成するために、拡張演算子を使用していることに注意してください。 これは、
arguments
関数プロパティの最新の代替です。
このすべてを実際に見てみましょう。
class Example { @log sum(a, b) { return a + b; } }
const e = new Example(); e.sum(1, 2); // Arguments: 1,2 // Result: 3
デコレータ関数のテキストでは、装飾されたメソッドを実行するために通常とは異なる構文を使用する必要があることに気付くかもしれません。 実際、これについては記事全体を書くことができますが、要するに、
apply
メソッドでは、
this
とそれに渡される引数を設定することで関数を呼び出すことができます。
さらに進むと、デコレータが自分の目的のために引数を受け入れるように、すべてを配置できます。 たとえば、
log
デコレータを次のように書き換えます。
function log(name) { return function decorator(t, n, descriptor) { const original = descriptor.value; if (typeof original === 'function') { descriptor.value = function(...args) { console.log(`Arguments for ${name}: ${args}`); try { const result = original.apply(this, args); console.log(`Result from ${name}: ${result}`); return result; } catch (e) { console.log(`Error from ${name}: ${e}`); throw e; } } } return descriptor; }; }
コードはより複雑になりましたが、対処すると、ここで次のことが発生することがわかります。
- 単一の引数
name
をとるlog
関数がありlog
。 - この関数は、デコレータである別の関数を返します。
返される関数は、外部関数からの
name
パラメーターを使用することを除いて、上で説明した
log
デコレーターと同じです。
これはすべて次のように使用できます。
class Example { @log('some tag') sum(a, b) { return a + b; } }
const e = new Example(); e.sum(1, 2); // Arguments for some tag: 1,2 // Result from some tag: 3
このアプローチにより、タグが割り当てられているため、ログ行を区別できることがすぐにわかります。
ここでは、フォーム
log('some tag')
関数が呼び出され、この呼び出しから返されたものが
sum
メソッドのデコレーターとして使用されます。
▍クラスデコレータ
クラスデコレータは、クラス定義全体に適用されます。 デコレータ関数は、デコードされたクラスコンストラクター関数である単一のパラメーターで呼び出されます。
デコレータは、作成時にクラスのすべてのインスタンスではなく、コンストラクター関数に適用されることに注意してください。 したがって、同じクラスの異なるインスタンスの異なるインスタンスを異なる方法で装飾するには、作成する前にコンストラクタを自分で装飾する必要があります。
一般に、クラスデコレータはクラスメンバデコレータよりも有用性が低くなります。実行できるのはクラスコンストラクタを置き換えるだけだからです。
ロギングのある例に戻り、コンストラクターのパラメーターを出力するデコレーターを作成します。
function log(Class) { return (...args) => { console.log(args); return new Class(...args); }; }
ここでは、引数として、クラスが受け入れられ、コンストラクターとして機能する新しい関数が返されます。 私たちのケースでは、単に引数を記録し、これらの引数で作成されたクラスの新しいインスタンスを返します。
例:
@log class Example { constructor(name, age) { } }
const e = new Example('Graham', 34); // [ 'Graham', 34 ] console.log(e); // Example {}
ご覧のとおり、
Example
クラスのコンストラクターを実行すると、このクラスのインスタンスの作成に使用されるコンストラクター引数がログに記録されます。 これはまさに私たちが目指していたものです。
クラスデコレータにパラメータを渡すには、すでに説明したアプローチを使用できます。
function log(name) { return function decorator(Class) { return (...args) => { console.log(`Arguments for ${name}: args`); return new Class(...args); }; } }
@log('Demo') class Example { constructor(name, age) {} }
const e = new Example('Graham', 34); // Arguments for Demo: args console.log(e); // Example {}
実際のプロジェクトのデコレータ
一般的なライブラリでデコレータを使用する例を次に示します。
▍コアデコレータライブラリ
すぐに使用できる汎用デコレータを提供する優れたコアデコレータライブラリがあります。 それらがサポートする機能には、メソッド呼び出しのタイミング、廃止された構成の通知、オブジェクトが不変かどうかのチェックがあります。
act Reactライブラリ
Reactは 、高次コンポーネントの概念をうまく利用しています。 これらは、関数として記述され、他のコンポーネントのラッパーとして機能するReactコンポーネントです。
これらは、デコレータとして使用するのに最小限の労力で済むため、デコレータとして使用するのに理想的な候補です。 たとえば、Reduxライブラリには、Reactコンポーネントの接続に使用される
connect
機能があります。 デコレータがない場合、この関数を使用すると次のようになります。
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
デコレータを使用してこれを書き換えると、次の結果が得られます。
@connect(mapStateToProps, mapDispatchToProps) export default class MyReactComponent extends React.Component {}
機能は同じであることが判明しましたが、すべてがより良く見えます。
▍MobXライブラリ
デコレータはMobXライブラリで広く使用されています。 たとえば、システムの目的の動作を確保するには、
Observable
または
Computed
デコレータをフィールドに追加し、
Observers
デコレータをクラスで使用するだけです。
まとめ
JavaScriptでデコレータを作成して使用する方法について話しました。特に、クラスメンバーのデコレータを操作する機能を調べました。 このアプローチにより、さまざまなクラスのメソッドの動作を変更するために使用できるデコレータ関数で表される補助コードを作成できます。 デコレータの構文を使用すると、プログラムのテキストを単純化し、よりわかりやすく、わかりやすくすることができます。 今日、JavaScriptでデコレータを操作できます;人気のあるライブラリでアプリケーションを見つけました。 ただし、ブラウザとNode.jsで直接サポートされた後、多くの新しいファンが見つかると考えています。
親愛なる読者! JSプロジェクトで既にデコレータを使用していますか?