JavaScriptのデコレーター。 What They Are and When to Use Them
On 1月 26, 2022 by adminES2015+ が導入され、トランスパイルが一般的になったので、多くの人が実際のコードやチュートリアルで新しい言語機能を目にするようになりました。 これらの機能のうち、最初に遭遇したとき、しばしば頭をかきむしるのが JavaScript デコレーターです。
Decorators は Angular 2+ で使用されているおかげで人気が出てきました。 Angular では、デコレーターは TypeScript のおかげで利用可能ですが、JavaScript では、現在ステージ 2 の提案です。 ここでは、デコレーターとは何か、そして、コードをよりすっきりさせ、より理解しやすくするためにデコレーターをどのように使用できるかを見ていきましょう。 これは、関数合成、または高階関数として以前に聞いたことがあるかもしれない概念です。
これは、多くのユースケースにおいて、単にある関数を呼び出して別の関数をラップすることにより、標準の JavaScript ですでに可能になっています。
doSomething('Graham');// Hello, Grahamwrapped('Graham');// Starting// Hello, Graham// Finished
How to Use JavaScript Decorators
Decorators use a special syntax in JavaScript, which they are prefixed with an @
symbol and placed immediately before the code being decorated.これは、JavaScript で特別な構文を使用します。
注意: 執筆の時点では、デコレーターは現在 “Stage 2 Draft” 形式で、ほとんど完成していますが、まだ変更される可能性があります。
たとえば、
@log()@immutable()class Example { @time('demo') doSomething() { // }}
これはクラスを定義し、3 つのデコレーター (2 つはクラス自体に、1 つはクラスのプロパティに) を適用します。
-
@log
はクラスへのすべてのアクセスを記録します。 -
@immutable
はクラスを不変にします – 新しいインスタンスでObject.freeze
を呼び出すなど -
@time
はメソッドの実行時間を記録して、固有のタグでこれをログアウトします。
現在のところ、現在のブラウザや Node リリースにはまだサポートがないため、デコレーターを使用するにはトランスパイラのサポートが必要です。 Babel を使用している場合、これは単に transform-decorators-legacy プラグインを使用することで有効になります。
注意: このプラグインで「レガシー」という言葉を使用しているのは、デコレーターを扱う Babel 5 方法をサポートしているからで、それが標準化されたときの最終形態と異なる可能性は十分にあります。
Why Use Decorators?
JavaScript ではすでに関数型の構成が可能ですが、他のコード部分 (たとえば、クラスやクラスのプロパティ) に同じ手法を適用することはかなり難しく、あるいは不可能にさえなっています。
Decorator の提案は、これらの問題を解決するために使用できるクラスおよびプロパティのデコレーターのサポートを追加し、将来の JavaScript バージョンではおそらく、コードの他の厄介な領域に対するデコレーター サポートが追加されるでしょう。
また、デコレーターは、コードの周りにこれらのラッパーを適用するためのきれいな構文を可能にし、結果として、書いていることの実際の意図を損なわないものにします。 これには、プロパティ、メソッド、ゲッター、およびセッターが含まれます。
デコレーターは実際には、別の関数を返し、装飾されるアイテムの適切な詳細とともに呼び出される関数以外の何ものでもありません。 これらのデコレーター関数は、プログラムが最初に実行されたときに一度だけ評価され、装飾されたコードは戻り値で置き換えられます。
クラスメンバーデコレーター
プロパティデコレーターは、クラスの単一のメンバー – それがプロパティ、メソッド、ゲッターまたはセッターであるかどうかに適用されます。
-
target
: そのメンバーがいるクラス。 -
name
: クラス内のメンバー名。 -
descriptor
: メンバー記述子です。 これは、基本的に Object.defineProperty に渡されるオブジェクトです。
ここで使用される古典的な例は、@readonly
です。
function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}
文字どおり、プロパティ記述子を更新して “writable” フラグを false に設定します。
これは、次に次のようにクラスのプロパティで使用します。 実際に装飾された関数を別の動作に置き換えることができる。 たとえば、すべての入力と出力を記録してみましょう。
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;}
これは、引数を記録し、元のメソッドを呼び出してから出力を記録する新しいメソッドでメソッド全体を置き換えます。
ここで、提供されたすべての引数から自動的に配列を構築するためにスプレッド演算子を使用したことに注意してください。 これだけでも 1 つの記事になりますが、簡単に言うと、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; };}
これはより複雑になっていますが、分解すると次のようになります:
- 1 つのパラメータを受け取る関数
log
。
これは、外側の関数から name
パラメータを使用することを除いて、先の log
デコレーターと同じです。
これは、log('some tag')
関数呼び出しが JavaScript ランタイムによって直ちに評価され、その応答が sum
メソッドのデコレーターとして使用されるために機能します。
これはコンストラクタ関数に適用され、作成されるクラスの各インスタンスには適用されないことに注意してください。
一般的に、これらはクラスメンバデコレータよりも有用性が低く、ここでできることはすべて、まったく同じ方法で単純な関数呼び出しで行うことができるためです。 これらを使用して行うことはすべて、最終的にクラス コンストラクタを置き換える新しいコンストラクタ関数を返す必要があります。
ロギングの例に戻り、コンストラクタ パラメータをログに記録するものを書いてみましょう。 これは、単に引数を記録し、それらの引数で構築されたクラスの新しいインスタンスを返します。
例:
@logclass Example { constructor(name, age) { }}const e = new Example('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: argsconsole.log(e);// Example {}
Real World Examples
Core Decorators
Core Decorators という素晴らしいライブラリがあり、すぐに使用できる非常に便利な共通デコレーターをいくつか提供してくれています。 これらは一般に、非常に便利な共通機能 (たとえば、メソッド呼び出しのタイミング、非推奨の警告、値が読み取り専用であることの確認など) を可能にしますが、はるかにすっきりしたデコレーター構文を使用します。 これは、関数として記述され、別のコンポーネントにラップする、単純な React コンポーネントです。 React The ES6 Way
これらは、デコレーターとして使用するための理想的な候補です。 たとえば、react-redux ライブラリには connect
という関数があり、React コンポーネントを Redux ストアに接続するために使用されます。
一般に、これは次のように使用されます。
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 としてマーキングできるようにしています。
Summary
クラス メンバー デコレーターは、独立した関数に対してすでにできる方法と非常によく似た方法で、クラス内部でコードをラップする非常によい方法を提供します。 これは、非常にクリーンで理解しやすい方法で多くの場所に適用できる、いくつかのシンプルなヘルパー コードを記述する良い方法を提供します。
コメントを残す