デコレータ

[説明] Decorator 提案は大幅な構文変更を受けており、現在は第 3 段階にあり、最終決定までに変更があるかどうかは不明です。この章は現在草案段階にあり、「新しい文法」とマークされている章はすべて現行の文法に基づいていますが、詳細にまとめられておらず、「新しい文法」とマークされていない章は一部の独自資料に基づいています。以前の文法と過去から残った原稿です。以前のコンテンツが保持される理由は 2 つあり、1 つは TypeScript デコレーターがこれらの構文を使用するため、もう 1 つは貴重なコンテンツが多く含まれているためです。規格が完全に完成したら、この章は完全に書き直され、古い内容が削除され、資料が補足され、説明が追加されます。 (2022年6月)

はじめに (新しい構文)

デコレータは JavaScript クラスの機能を強化するために使用されます。現在、ECMAScript に 提案書 が導入されています。

デコレーターとは、「@+関数名」で記述される関数で、4種類の値を修飾するために使用できます。

  • 親切
  • クラスの属性
  • クラスメソッド
  • 属性アクセサー (アクセサー)

以下の例では、クラス名とクラスメソッド名の前にデコレータが置かれていることが分かります。

@frozen クラス Foo {
  @configurable(false)
  @enumerable(true)
  方法() {}

  @スロットル(500)
  高価なメソッド() {}
}

上記のコードでは、合計 4 つのデコレーターを使用しています。1 つはクラス自体 (@frozen) 用で、他の 3 つはクラス メソッド (@configurable()、@enumerable()、@throttle()) 用です。これらは、コードの可読性を高め、意図を明確に表現するだけでなく、クラスの機能を追加または変更するための便利な手段も提供します。

デコレータ API (新しい構文)

デコレータは関数であり、APIの種類は以下のように記述されます(TypeScriptの記述方法)。

type Decorator = (値: 入力、コンテキスト: {
  種類: 文字列;
  名前: 文字列記号;
  アクセス: {
    get?(): 不明;
    セット?(値: 不明): 無効;
  };
  プライベート?: ブール値;
  静的?: ブール値;
  addInitializer?(初期化子: () => void): void;
}) => 出力 void |

デコレータ関数は 2 つのパラメータを取ります。実行時に、JavaScript エンジンはこれら 2 つのパラメーターを提供します。

  • value: 修飾される値。場合によっては (属性を修飾する場合)、「未定義」になる可能性があります。
  • context: コンテキスト情報オブジェクト。

デコレーター関数の戻り値は、装飾されたオブジェクトの新しいバージョンですが、値を返さない場合もあります (void)。

contextオブジェクトには多くの属性がありますが、その中でどの装飾に属するかを示す属性が「kind」属性です。その他の属性の意味は以下の通りです。

  • kind: 装飾タイプを示す文字列。可能な値は classmethodgettersetterfieldaccessor です。
  • name: 装飾された値の名前: 値の名前、またはプライベート要素の場合はその説明 (読み取り可能な名前など)。
  • access: この値にアクセスするためのメソッド、つまりセーバーとゲッターを含むオブジェクト。
  • static: ブール値、値が静的要素かどうか。
  • private: ブール値、値がプライベート要素かどうか。
  • addInitializer: ユーザーが初期化ロジックを追加できるようにする関数。

デコレーターの実行手順は次のとおりです。

  1. 各デコレータの値を左から右、上から下の順に計算します。
  2. メソッド デコレータを呼び出します。
  3. クラス デコレータを呼び出します。

クラスの装飾

デコレータを使用してクラス全体を装飾できます。

@testable
クラス MyTestableClass {
  // ...
}

関数 testable(ターゲット) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

上記のコードでは、@testable がデコレータです。これはクラス MyTestableClass の動作を変更し、静的属性 isTstable を追加します。 testable 関数のパラメータ target は、MyTestableClass クラスそのものです。

基本的に、デコレーターは次のように動作します。

@デコレータ
クラス A {}

// と同等

クラス A {}
A = デコレータ(A) || A;

つまり、デコレータはクラスを処理する関数です。デコレーター関数の最初のパラメーターは、装飾されるターゲット クラスです。

関数 testable(ターゲット) {
  // ...
}

上記のコードでは、testable 関数のパラメータ target が装飾されるクラスです。

1 つのパラメーターでは十分ではないと思われる場合は、デコレーターの外側に関数の別の層をカプセル化できます。

関数 testable(isTestable) {
  戻り関数(ターゲット) {
    target.isTestable = isTestable;
  }
}

@testable(true)
クラス MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
クラス MyClass {}
MyClass.isTestable // false

上記のコードでは、デコレータ「testable」はパラメータを受け入れることができます。これは、デコレータの動作を変更できることを意味します。

デコレータは、実行時ではなく、コードのコンパイル時にクラスの動作を変更することに注意してください。これは、デコレータがコンパイル中にコードを実行できることを意味します。言い換えれば、デコレーターは本質的にコンパイル時に実行される関数です。

前の例では、クラスに静的属性を追加しました。インスタンス属性を追加したい場合は、ターゲット クラスの「prototype」オブジェクトを通じて操作できます。

関数 testable(ターゲット) {
  target.prototype.isTestable = true;
}

@testable
クラス MyTestableClass {}

let obj = new MyTestableClass();
obj.isTestable // true

上記のコードでは、デコレータ関数 testable がターゲット クラスの prototype オブジェクトに属性を追加しているため、インスタンス上で呼び出すことができます。

別の例を示します。

// ミックスイン.js
エクスポート関数 mixins(...list) {
  戻り関数 (ターゲット) {
    Object.assign(target.prototype, ...list)
  }
}

// メイン.js
'./mixins.js' から { mixins } をインポートします

const Foo = {
  foo() { console.log('foo') }
};

@mixins(フー)
クラス MyClass {}

let obj = new MyClass();
obj.foo() // 'foo'

上記のコードは、デコレータ mixins を通じて Foo オブジェクトのメソッドを MyClass のインスタンスに追加します。この機能は「Object.assign()」を使用してシミュレートできます。

const Foo = {
  foo() { console.log('foo') }
};

クラス MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

実際の開発において、ReactをReduxライブラリと組み合わせて使用​​する場合、以下のように記述する必要がある場合が多いです。

class MyReactComponent extends React.Component {}

デフォルトの接続をエクスポート(mapStateToProps, mapDispatchToProps)(MyReactComponent);

デコレータを使用すると、上記のコードを書き換えることができます。

@connect(mapStateToProps、mapDispatchToProps)
デフォルト クラス MyReactComponent をエクスポートして React.Component を拡張します {}

比較的、後者の書き方の方が分かりやすいと思います。

クラスデコレーター (新しい構文)

クラス デコレータの種類については以下で説明します。

type ClassDecorator = (値: 関数、コンテキスト: {
  種類: "クラス";
  名前: 文字列 | 未定義;
  addInitializer(初期化: () => void): void;
}) => 関数 void;

クラス デコレータの最初のパラメータは、装飾されるクラスです。 2 番目のパラメータはコンテキスト オブジェクトです。装飾されたクラスが匿名クラスの場合、name 属性は unknown です。

クラス デコレータは、元のクラスを置き換えて新しいクラスを返すことも、値を返さないこともできます。返されたものがコンストラクターではない場合、エラーが報告されます。

以下に例を示します。

関数 logged(値, { 種類, 名前 }) {
  if (種類 === "クラス") {
    戻りクラスは値を拡張します {
      コンストラクター(...args) {
        super(...args);
        console.log(`引数 ${args.join(", ")}` を使用して ${name} のインスタンスを構築します);
      }
    }
  }

  // ...
}

@ログに記録されました
クラス C {}

新しい C(1);
// 引数 1 を使用して C のインスタンスを構築します

デコレータが使用されない場合、クラス デコレータは実際に次の構文を実行します。

クラス C {}

C = ログ(C, {
  種類: "クラス"、
  名前:「C」、
})??C;

新しい C(1);

メソッド デコレータ (新しい構文)

メソッド デコレータはクラス メソッドを変更します。

クラスC {
  @トレース
  toString() {
    「C」を返します。
  }
}

// と同等
C.prototype.toString = トレース(C.prototype.toString);

上記の例では、toString() メソッドを @trace で修飾することは、メソッドを変更することと同じです。

メソッド デコレータは TypeScript を使用して次のように型を記述します。

type ClassMethodDecorator = (値: 関数、コンテキスト: {
  種類: "メソッド";
  名前: 文字列記号;
  アクセス: { get(): 不明 };
  静的: ブール値;
  プライベート: ブール値;
  addInitializer(初期化: () => void): void;
}) => 関数 void;

メソッド デコレーターの最初のパラメーター value は、装飾されるメソッドです。

メソッド デコレーターは、元のメソッドを置き換える新しい関数を返すことができますが、元のメソッドがまだ使用されていることを示す値を返すことはできません。他の型の値が返された場合、エラーが報告されます。以下に例を示します。

関数 replaceMethod() {
  戻り関数 () {
    return `調子はどうですか、${this.name}?`;
  }
}

クラス人 {
  コンストラクター(名前) {
    this.name = 名前;
  }
  @replaceMethod
  こんにちは() {
    return `こんにちは ${this.name}!`;
  }
}

const robin = 新しい人('ロビン');

robin.hello(), '調子はどうですか、ロビン?'

上の例では、@replaceMethod は新しい関数を返し、元の hello() メソッドを置き換えます。

関数 logged(値, { 種類, 名前 }) {
  if (種類 === "メソッド") {
    戻り関数 (...args) {
      console.log(`${name} を引数 ${args.join(", ")}` で開始);
      const ret = value.call(this, ...args);
      console.log(`end ${name}`);
      retを返します。
    };
  }
}

クラスC {
  @ログに記録されました
  m(引数) {}
}

新しい C().m(1);
// m を引数 1 から開始します
// 終わりのm

上の例では、デコレータ @logged は元の m() メソッドの代わりに関数を返します。

ここでのデコレータは実際には糖衣構文であり、実際の操作はプロトタイプ チェーンの m() メソッドを次のように変更することです。

クラスC {
  m(引数) {}
}

C.prototype.m = logged(C.prototype.m, {
  種類: 「メソッド」、
  名前:「m」、
  静的: false、
  プライベート: false、
}) ?? C.prototype.m;

メソッドの装飾

デコレータはクラスを装飾するだけでなく、クラスのプロパティを装飾することもできます。

クラス人 {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

上記のコードでは、デコレータ readonly を使用して、「class」の name メソッドを装飾しています。

デコレータ関数 readonly は、合計 3 つのパラメータを受け入れることができます。

function readonly(ターゲット, 名前, 記述子){
  //記述子オブジェクトの元の値は次のとおりです
  // {
  // 値: 指定された関数、
  // 列挙可能: false,
  // 設定可能: true,
  // 書き込み可能: true
  // };
  記述子.writable = false;
  戻り記述子。
}

readonly(Person.prototype, 'name', 記述子);
// に似ています
Object.defineProperty(person.prototype, 'name', 記述子);

デコレータの最初のパラメータはクラスのプロトタイプ オブジェクトです。上記の例は person.prototype です。デコレータの本来の目的はクラスのインスタンスを「装飾する」ことですが、この時点ではインスタンスはまだ装飾されていません。生成されるため、プロトタイプを修飾することしかできません (これはクラスの修飾とは異なり、この場合、「target」パラメーターは修飾される属性の名前であり、3 番目のパラメーターは属性の説明オブジェクト。

さらに、上記のコードは、デコレーター (読み取り専用) が属性の説明オブジェクト (記述子) を変更し、変更された説明オブジェクトを使用して属性を定義することを示しています。

以下は、プロパティ記述オブジェクトの enumerable プロパティを変更して、プロパティを走査できないようにする別の例です。

クラス人 {
  @nonenumerable
  get kidCount() { this.children.length を返す }
}

function nonenumerable(ターゲット, 名前, 記述子) {
  記述子.enumerable = false;
  戻り記述子。
}

以下の@logデコレータはログを出力する役割を果たすことができます。

クラス数学 {
  @ログ
  add(a, b) {
    a + b を返します。
  }
}

関数 log(ターゲット、名前、記述子) {
  var oldValue = 記述子.値;

  記述子.値 = function() {
    console.log(`${name} を`, 引数で呼び出す);
    return oldValue.apply(this, argument);
  };

  戻り記述子。
}

const math = new Math();

// 渡されたパラメータはログに記録されるはずです
math.add(2, 4);

上記のコードでは、@log デコレータの機能は、ログを出力するという目的を達成するために、元の操作を実行する前に console.log を 1 回実行することです。

デコレータにはアノテーションの機能があります。

@testable
クラス人 {
  @readonly
  @nonenumerable
  name() { return `${this.first} ${this.last}` }
}

上記のコードから、person クラスはテスト可能であるのに対し、name メソッドは読み取り専用で列挙不可能であることが一目でわかります。

以下は Decorator (https://github.com/ionic-team/stencil) を使用して書かれた [コンポーネント] です。一見すると明確に見えます。

@成分({
  タグ: '私のコンポーネント'styleUrl: 'my-component.scss'
})
エクスポートクラス MyComponent {
  @Prop() 最初: 文字列;
  @Prop() 最後: 文字列;
  @State() isVisible: ブール値 = true;

  与える() {
    戻る (
      <p>こんにちは、私の名前は {this.first} {this.last} です</p>
    );
  }
}

同じメソッドに複数のデコレータがある場合、玉ねぎの皮をむくようなもので、最初に外側から内側に入り、次に内側から外側に実行されます。

関数 dec(id){
  console.log('評価済み', id);
  return (ターゲット、プロパティ、記述子) => console.log('実行済み', id);
}

クラスの例 {
    @dec(1)
    @dec(2)
    方法(){}
}
// 1 を評価
// 2 を評価
// 2を実行
// 実行1

上記のコードでは、外側のデコレータ @dec(1) が最初に入力されますが、内側のデコレータ @dec(2) が最初に実行されます。

注釈に加えて、デコレータは型チェックにも使用できます。したがって、クラスの場合、この機能は非常に便利です。長期的には、JavaScript コードの静的分析のための重要なツールとなるでしょう。

関数ではデコレータを使用できないのはなぜですか?

デコレータはクラスとクラス メソッドでのみ使用できます。関数ホイスティングのため、関数では使用できません。

var カウンタ = 0;

var add = function () {
  カウンタ++;
};

@追加
関数 foo() {
}

上記のコードは、実行後に counter が 1 に等しいことを意図していますが、実際の結果は counter が 0 に等しいことになります。関数昇格のため、実際に実行されるコードは以下の通りです。

var カウンタ。
var を追加します。

@追加
関数 foo() {
}

カウンタ = 0;

追加 = 関数 () {
  カウンタ++;
};

別の例を示します。

var readOnly = require("some-decorator");

@readOnly
関数 foo() {
}

実際の実行は次のとおりであるため、上記のコードにも問題があります。

var readOnly;

@readOnly
関数 foo() {
}

readOnly = require("some-decorator");

つまり、関数の巻き上げのため、デコレーターを関数で使用することはできません。クラスの昇格はありませんので問題ありません。

一方、関数を装飾する必要がある場合は、高階関数の形式で直接実行できます。

関数 doSomething(名前) {
  console.log('こんにちは' + 名前);
}

関数loggingDecorator(ラップされた) {
  戻り関数() {
    console.log('開始');
    const result = ラップされた.apply(this, argument);
    console.log('完了');
    結果を返します。
  }
}

const ラップ =loggingDecorator(doSomething);

アクセサ デコレータ (新しい構文)

アクセサ デコレータは TypeScript を使用して、次のように型を記述します。

type ClassGetterDecorator = (値: 関数、コンテキスト: {
  種類: "ゲッター";
  名前: 文字列記号;
  アクセス: { get(): 不明 };
  静的: ブール値;
  プライベート: ブール値;
  addInitializer(初期化: () => void): void;
}) => 関数 void;

type ClassSetterDecorator = (値: 関数、コンテキスト: {
  種類: "セッター";
  名前: 文字列記号;
  アクセス: { set(値: 不明): void };
  静的: ブール値;
  プライベート: ブール値;
  addInitializer(初期化: () => void): void;
}) => 関数 void;

アクセサー デコレーターの最初のパラメーターは、元のセッターとゲッターです。

アクセサー デコレーターの戻り値が関数の場合、元のアクセサーが置き換えられます。基本的に、メソッド デコレータと同様に、変更はクラスのプロトタイプ オブジェクトに対して行われます。値を返さずに元のアクセサーを使用し続けることもできます。他の型の値が返された場合、エラーが報告されます。

アクセサ デコレータはセッターとゲッターに対して個別に動作します。次の例では、@fooget x() を修飾するだけで、set x() は修飾しません。

クラスC {
  @foo
  get x() {
    // ...
  }

  set x(val) {
    // ...
  }
}

前のセクションの @logged デコレーターは、わずかな変更を加えてアクセス デコレーターとして使用できます。

関数 logged(値, { 種類, 名前 }) {
  if (kind === "メソッド" || kind === "getter" || kind === "setter") {
    戻り関数 (...args) {
      console.log(`${name} を引数 ${args.join(", ")}` で開始);
      const ret = value.call(this, ...args);
      console.log(`end ${name}`);
      retを返します。
    };
  }
}

クラスC {
  @ログに記録されました
  set x(arg) {}
}

新しい C().x = 1
// 引数 1 で x を開始します
// 終了 x

構文シュガーを削除し、従来の構文を使用して記述すると、クラスのプロトタイプ チェーンが変更されます。

クラスC {
  set x(arg) {}
}

let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");
set = logged(set, {
  種類: 「セッター」、
  名前: "x"、
  静的: false、
  プライベート: false、
}) ?? セット;

Object.defineProperty(C.prototype, "x", { set });

プロパティ デコレータ (新しい構文)

プロパティ デコレータの種類については以下で説明します。

type ClassFieldDecorator = (値: 未定義、コンテキスト: {
  種類: "フィールド";
  名前: 文字列記号;
  アクセス: { get(): 不明、set(値: 不明): void };
  静的: ブール値;
  プライベート: ブール値;
}) => (初期値: 不明) => 不明 |

プロパティ デコレータの最初のパラメータは「未定義」です。これは、値が入力されていないことを意味します。ユーザーは、プロパティに値が割り当てられると、デコレーターが初期化関数を返すように選択でき、この初期化関数はプロパティの初期値を受け取り、新しい初期値を返します。プロパティ デコレーターは値を返さないこともあります。これら 2 つの場合を除き、他の型の値を返すとエラーになります。

以下に例を示します。

関数 logged(値, { 種類, 名前 }) {
  if (種類 === "フィールド") {
    戻り関数 (初期値) {
      console.log(`${name} を値 ${initialValue} で初期化中`);
      初期値を返します。
    };
  }

  // ...
}

クラスC {
  @logged x = 1;
}

新しい C();
// x を値 1 で初期化します

デコレータ構文を使用しない場合、プロパティ デコレータの実際の効果は次のようになります。

letInitializeX = logged(未定義, {
  種類: 「フィールド」、
  名前: "x"、
  静的: false、
  プライベート: false、
}) ?? (初期値) => 初期値;

クラスC {
  x = 初期化X.call(this, 1);
}

アクセサコマンド (新しい構文)

クラス デコレーターは、プロパティに接頭辞を付けるための新しいコマンド accessor を導入します。

クラスC {
  アクセサ x = 1;
}

これは、プロパティ x がプライベート プロパティ #x へのアクセス インターフェイスであると宣言するのと同等の短縮形式です。上記のコードは、以下のコードと同等です。

クラスC {
  #x = 1;

  get x() {
    これを返します。#x;
  }

  set x(val) {
    this.#x = val;
  }
}

「accessor」コマンドの前に、「static」コマンドと「private」コマンドを追加することもできます。

クラスC {
  静的アクセサー x = 1;
  アクセサ #y = 2;
}

accessor コマンドは、その前に属性デコレータを受け入れることもできます。

関数 logged(値, { 種類, 名前 }) {
  if (種類 === "アクセサ") {
    let { get, set } = 値;

    戻る {
      得る() {
        console.log(`${name} の取得`);

        get.call(this) を返します。
      }、

      set(val) {
        console.log(`${name}${val} に設定します`);

        return set.call(this, val);
      }、

      init(初期値) {
        console.log(`${name} を値 ${initialValue} で初期化中`);
        初期値を返します。
      }
    };
  }

  // ...
}

クラスC {
  @logg アクセサー x = 1;
}

c = 新しい C(); とします。
// x を値 1 で初期化します
c.x;
// x を取得する
c.x = 123;
// x を 123 に設定します

上記の例は、@logged デコレータを使用して、accessor 属性の getter メソッドと setter メソッドをオーバーライドするのと同じです。

「accessor」に使用されるプロパティ デコレータの種類を以下に説明します。

type ClassAutoAccessorDecorator = (
  価値: {
    取得: () => 不明;
    set(値: 不明) => void;
  }、
  コンテクスト: {
    種類: "アクセサ";
    名前: 文字列記号 |
    アクセス: { get(): 不明、set(値: 不明): void };
    静的: ブール値;
    プライベート: ブール値;
    addInitializer(初期化: () => void): void;
  }
) => {
  取得?: () => 不明;
  set?: (値: 不明) => void;
  初期化?: (初期値: 不明) => 不明;
無効;

accessor コマンドの最初のパラメーターは、accessor コマンドによって定義されたプロパティのアクセサーの get および set を含むオブジェクトを受け取ります。プロパティ デコレーターは、元のアクセサーを置き換える新しいアクセサーを含む新しいオブジェクトを返すことができます。これは、元のアクセサーをインターセプトすることと同じです。さらに、返されるオブジェクトには、プライベート プロパティの初期値を変更するために使用される initialize 関数を含めることもできます。デコレータは値を返す必要はありません。他の型の値や他のプロパティを含むオブジェクトを返す場合、エラーが報告されます。

addInitializer() メソッド (新しい構文)

プロパティ デコレータに加えて、他のデコレータのコンテキスト オブジェクトにも、初期化操作を完了するための addInitializer() メソッドが含まれています。

その実行時間は次のとおりです。

  • クラス デコレータ: クラスが完全に定義された後。
  • メソッド デコレーター: クラスの構築中、プロパティが初期化される前に実行されます。
  • 静的メソッド デコレーター: クラス定義中に、静的プロパティの定義より前、クラス メソッドの定義より後に実行されます。

以下に例を示します。

関数カスタム要素(名前) {
  return (値, { addInitializer }) => {
    addInitializer(function() {
      CustomElements.define(名前, これ);
    });
  }
}

@customElement('私の要素')
クラス MyElement extends HTMLElement {
  静的取得observedAttributes() {
    return ['some', 'attrs'];
  }
}

上記のコードは、デコレータを除いた以下のコードと同等です。

クラス MyElement {
  静的取得observedAttributes() {
    return ['some', 'attrs'];
  }
}

イニシャライザForMyElement = [];

MyElement = カスタムElement('my-element')(MyElement, {
  種類: "クラス"、
  名前: "MyElement"addInitializer(fn) {
    初期化子ForMyElement.push(fn);
  }、
}) ??MyElement;

for (initializersForMyElement の let イニシャライザ) {
  初期化子.call(MyElement);
}

以下はメソッド デコレータの例です。

functionbound(value, { name, addInitializer }) {
  addInitializer(関数() {
    this[名前] = この[名前].bind(this);
  });
}

クラスC {
  メッセージ = "こんにちは!";

  @bound
  m() {
    console.log(this.message);
  }
}

let { m } = 新しい C();

m(); // こんにちは!

上記のコードは、デコレータを除いた以下のコードと同等です。

クラスC {
  コンストラクター() {
    for (initializersForM の let イニシャライザ) {
      初期化子.call(this);
    }

    this.message = "こんにちは!";
  }

  m() {}
}

letInitializersForM = []

C.prototype.m = バウンド(
  C.プロトタイプ.m、
  {
    種類: 「メソッド」、
    名前:「m」、
    静的: false、
    プライベート: falseaddInitializer(fn) {
      初期化子ForM.push(fn);
    }、
  }
)??C.プロトタイプ.m;

core-decorators.js

core-decorators.js は、デコレータをより深く理解できるようにするためのいくつかの一般的なデコレータを提供するサードパーティ モジュールです。

(1)@autobind

「autobind」デコレータは、メソッド内の「this」オブジェクトを元のオブジェクトにバインドします。

import { autobind } から 'core-decorators';

クラス人 {
  @自動バインド
  getパーソン() {
    これを返します。
  }
}

人 = 新しい人(); にします。
getPerson = person.getperson; にします。

getPerson() === 人;
// 真実

(2)@readonly

「readonly」デコレータは、プロパティまたはメソッドを書き込み不可能にします。

import { readonly } から 'core-decorators';

クラスの食事{
  @readonly
  前菜 = 'ステーキ';
}

var ディナー = 新しい食事();
ディナー.エントリー = 'サーモン';
// [object Object] の読み取り専用プロパティ 'entre' に割り当てることはできません

(3)@オーバーライド

override デコレータは、サブクラスのメソッドが親クラスの同じ名前のメソッドを正しくオーバーライドしているかどうかをチェックし、正しくない場合はエラーが報告されます。

import { override } from 'core-decorators';

親クラス {
  話す(最初、二番目) {}
}

class Child extends Parent {
  @オーバーライド
  話す() {}
  // SyntaxError: Child#speak() は Parent#speak(first, Second) を適切にオーバーライドしません
}

// または

class Child extends Parent {
  @オーバーライド
  話す() {}
  // SyntaxError: Child#speaks() に一致する記述子がプロトタイプ チェーン上に見つかりませんでした。
  //
  // 「話す」という意味ですか?
}

(4)@deprecate (エイリアス @deprecated)

deprecate または deprecated デコレーターは、メソッドが非推奨になることを示す警告をコンソールに表示します。

import { deprecate } from 'core-decorators';

クラス人 {
  @非推奨
  facepalm() {}

  @deprecate('フェイスパーミングをやめました')
  facepalmHard() {}

  @deprecate('フェイスパーミングを停止しました', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

人 = 新しい人(); にします。

person.facepalm();
// 非推奨 Person#facepalm: この関数は将来のバージョンでは削除される予定です。

person.facepalmHard();
// 非推奨 Person#facepalmHard: フェイスパームを停止しました

person.facepalmHarder();
// 非推奨 Person#facepalmHarder: フェイスパームをやめました
//
// 詳細については、http://knowyourmeme.com/memes/facepalm を参照してください。
//

(5)@suppressWarnings

suppressWarnings デコレータは、deprecated デコレータによって引き起こされる console.warn() 呼び出しを抑制します。ただし、非同期コードによる呼び出しは例外です。

import {suppressWarnings} から 'core-decorators';

クラス人 {
  @非推奨
  facepalm() {}

  @suppressWarning
  facepalmWithoutWarning() {
    this.facepalm();
  }
}

人 = 新しい人(); にします。

person.facepalmWithoutWarning();
// 警告はログに記録されません

デコレータを使用してイベントを自動的に公開する

デコレータを使用すると、オブジェクトのメソッドが呼び出されたときにイベントを自動的に発行できます。

const postal = require("postal/lib/postal.lodash");

デフォルト関数のエクスポート public(トピック、チャネル) {
  const チャンネル名 = チャンネル '/';
  const msgChannel = postal.channel(チャンネル名);
  msgChannel.subscribe(トピック, v => {
    console.log('チャンネル: ', チャンネル名);
    console.log('イベント: ', トピック);
    console.log('データ: ', v);
  });

  return function(ターゲット、名前、記述子) {
    const fn = 記述子.値;

    記述子.値 = function() {
      let value = fn.apply(this, argument);
      msgChannel.publish(トピック, 値);
    };
  };
}

上記のコードは、publish という名前のデコレータを定義します。これは、元のメソッドが呼び出されたときにイベントが自動的に発行されるように、descriptor.value を書き換えます。使用するイベント「publish/subscribe」ライブラリは Postal.js です。

使い方は以下の通りです。

// インデックス.js
「./publish」からインポート・パブリッシュ;

クラス FooComponent {
  @publish('foo.some.message', 'component')
  someMethod() {
    return { my: 'データ' };
  }
  @publish('foo.some.other')
  anotherMethod() {
    // ...
  }
}

let foo = new FooComponent();

foo.someMethod();
foo.anotherMethod();

今後、someMethod または anotherMethod が呼び出されるたびに、イベントが自動的に発行されるようになります。

$ bash-nodeindex.js
チャンネル: コンポーネント
イベント: foo.some.message
データ: { 私の: 'データ' }

チャンネル: /
イベント: foo.some.other
データ: 未定義

ミックスイン

デコレータに基づいて、「Mixin」モードを実装できます。いわゆる「ミックスイン」モードは、オブジェクト継承の代替手段であり、中国語訳は「ミックスイン」であり、あるオブジェクトを別のオブジェクトに混合する方法を意味します。

以下の例を参照してください。

const Foo = {
  foo() { console.log('foo') }
};

クラス MyClass {}

Object.assign(MyClass.prototype, Foo);

let obj = new MyClass();
obj.foo() // 'foo'

上記のコードでは、オブジェクト Foo には foo メソッドがあります。 Object.assign メソッドを通じて、foo メソッドを MyClass クラスに「混合」することができ、これにより MyClass のすべてのインスタンスが生成されます。および obj オブジェクトに foo メソッドを持たせます。これは、「ミックスイン」モードの単純な実装です。

次に、共通スクリプト「mixins.js」をデプロイし、デコレータとしてMixinを記述します。

エクスポート関数 mixins(...list) {
  戻り関数 (ターゲット) {
    Object.assign(target.prototype, ...list);
  };
}

次に、上記のデコレータを使用して、さまざまなメソッドをクラスに「混合」できます。

'./mixins.js' から { mixins } をインポートします。

const Foo = {
  foo() { console.log('foo') }
};

@mixins(フー)
クラス MyClass {}

let obj = new MyClass();
obj.foo() // "foo"

mixins デコレータを通じて、MyClass クラス上の Foo オブジェクトを「混合」する foo メソッドが実装されます。

ただし、上記のメソッドは MyClass クラスの prototype オブジェクトを上書きします。これが気に入らない場合は、クラス継承を通じて Mixin を実装することもできます。

クラス MyClass extends MyBaseClass {
  /* ... */
}

上記のコードでは、「MyClass」は「MyBaseClass」を継承します。 foo メソッドを MyClass に「混合」したい場合、1 つの方法は、MyClassMyBaseClass の間にミックスイン クラスを挿入することです。このクラスは foo メソッドを持ち、MyBaseClass を継承します。すべてのメソッドと MyClass がこのクラスを継承します。

let MyMixin = (スーパークラス) => クラスはスーパークラスを拡張します {
  foo() {
    console.log('MyMixin からの foo');
  }
};

上記のコードで、MyMixin は、パラメータとして superclass を受け取り、superclass を継承し、foo メソッドを含むサブクラスを返すミックスイン クラス ジェネレーターです。

そして、ターゲットクラスはこのミックスインクラスを継承し、それによって foo メソッドを「ミックスイン」するという目的を達成します。

class MyClass extends MyMixin(MyBaseClass) {
  /* ... */
}

let c = new MyClass();
c.foo(); // "MyMixin からの foo"

複数のメソッドを「混合」する必要がある場合は、複数の混合クラスを生成します。

class MyClass extends Mixin1(Mixin2(MyBaseClass)) {
  /* ... */
}

この書き方の利点の 1 つは、「super」を呼び出すことができるため、「混合」プロセス中に親クラスの同じ名前のメソッドをオーバーライドすることを回避できることです。

let Mixin1 = (スーパークラス) => クラスはスーパークラスを拡張します {
  foo() {
    console.log('Mixin1 からの foo');
    if (super.foo) super.foo();
  }
};

let Mixin2 = (スーパークラス) => クラスはスーパークラスを拡張します {
  foo() {
    console.log('Mixin2 の foo');
    if (super.foo) super.foo();
  }
};

クラスS {
  foo() {
    console.log('S からの foo');
  }
}

クラス C extends Mixin1(Mixin2(S)) {
  foo() {
    console.log('C からの foo');
    super.foo();
  }
}

上記のコードでは、「混合」が発生するたびに親クラスの「super.foo」メソッドが呼び出されるため、親クラスの同名のメソッドは上書きされずに動作が保持されます。

新しい C().foo()
// C からの foo
// Mixin1 からの foo
// Mixin2 からの foo
// S からの foo

特性

Trait も一種のデコレータです。その効果は Mixin に似ていますが、同じ名前のメソッドの競合を防止したり、特定のメソッドの混合を除外したり、混合メソッドのエイリアスを設定したりするなど、より多くの機能を提供します。

以下では、例としてサードパーティ モジュール traits-decorator を使用します。このモジュールが提供する traits デコレータは、オブジェクトだけでなく、ES6 クラスもパラメータとして受け入れることができます。

'traits-decorator' から { traits } をインポートします。

クラスTFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') }
};

@traits(TFooTBar)
クラス MyClass { }

let obj = new MyClass();
obj.foo() // foo
obj.bar() // バー

上記のコードでは、TFoo クラスの foo メソッドと TBar オブジェクトの bar メソッドが、traits デコレータを通じて MyClass クラスに「混合」されています。

特性では、同じ名前のメソッドを「混合」することはできません。

'traits-decorator' から { traits } をインポートします。

クラスTFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

@traits(TFooTBar)
クラス MyClass { }
// エラーを報告する
// throw new Error('メソッド名: ' + MethodName + ' は 2 回定義されています。');
// ^
// エラー: メソッド名: foo が 2 回定義されています。

上記のコードでは、TFooTBar の両方に foo メソッドがあり、その結果、traits デコレータがエラーを報告します。

回避策の 1 つは、TBar の foo メソッドを除外することです。

import { traits, excludes } from 'traits-decorator';

クラスTFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

@traits(TFoo, TBar::excludes('foo'))
クラス MyClass { }

let obj = new MyClass();
obj.foo() // foo
obj.bar() // バー

上記のコードはバインディング演算子 (::) を使用して TBarfoo メソッドを除外しているため、混在してもエラーは報告されません。

別の方法は、TBarfoo メソッドにエイリアスを与えることです。

import { traits, alias } from 'traits-decorator';

クラスTFoo {
  foo() { console.log('foo') }
}

const TBar = {
  bar() { console.log('bar') },
  foo() { console.log('foo') }
};

@traits(TFoo, TBar::alias({foo: 'aliasFoo'}))
クラス MyClass { }

let obj = new MyClass();
obj.foo() // foo
obj.aliasFoo() // foo
obj.bar() // バー

上記のコードは、TBarfoo メソッドのエイリアス aliasFoo を作成しているため、MyClassTBarfoo メソッドに混合することもできます。

「alias」メソッドと「excludes」メソッドは組み合わせて使用​​できます。

@traits(TExample::excludes('foo','bar')::alias({baz:'exampleBaz'}))
クラス MyClass {}

上記のコードは、TExamplefoo メソッドと bar メソッドを除外し、baz メソッドにエイリアス exampleBaz を与えます。

as メソッドは、上記のコードを記述する別の方法を提供します。

@traits(TExample::as({excludes:['foo', 'bar'], エイリアス: {baz: 'exampleBaz'}}))
クラス MyClass {}

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0