【TypeScript】【デコレータ】について

はじめに

この記事は【TypeScript】+【デコレータ】についての備忘録である。

【デコレータ】とは

デコレータとはメタプログラミングに役に立つ機能である。
メタプログラミングとは、ユーザが直接触ったり見たりする機能には使われないが、開発者が使いやすい道具を提供することに向いている。
JavaのSpringなどを触ったことの有る方はアノテーション@ がイメージしやすい。

  • デコレータはメタプログラミングに役立つ機能
  • メタプログラミングとは
    • ユーザが直接触ったり見たりする機能には使われず、開発者が使いやすい道具を提供することに向いている
    • クラスやクラスのメソッドが正しく使われることを保証
    • 表向きには見えない変換処理を行う
  • デコレータはあてる場所によって受け取れる引数などが変わる

Decorators公式ドキュメント

デコレータを追加できる場所

デコレータは Class 内のほぼ全てに追加できる。

  • class
  • property
  • accessor(getter/setter)
  • method
  • parameter
クラス・デコレータ: Class Decorators

クラス宣言の直前に宣言される。
実行時に関数として呼び出され、デコレータされたクラスのコンストラクターが唯一の引数として使用される。
以下の例では @sealed デコレータは BugReport クラスに適用され、そのクラスとプロトタイプがシールド(変更不可)になります。

// デコレータ
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

// クラス・デコレータ使用箇所
@sealed
class BugReport {
  type = "report";
  title: string;
  constructor(t: string) {
    this.title = t;
  }
}
プロパティ・デコレータ: Property Decorators

プロパティ宣言の直前に宣言される。
実行時に次の 2 つの引数を持つ関数として呼び出される。

  1. 静的メンバー(static)の場合はクラスのコンストラクター関数、インスタンスメンバー(static以外)の場合はクラスのプロトタイプのいずれか。
  2. プロパティ名

この例では @format デコレータは greeting プロパティに適用され、そのプロパティがアクセスされる際にフォーマットされた文字列が返されるようになる。

// デコレータ
function format(formatString: string) {
  return function (target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
      get: function () {
        return formatString.replace("%s", this[propertyKey]);
      },
    });
  };
}
class Greeter {
  // プロパティ・デコレータ使用箇所
  @format("Hello, %s")
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}
メソッド・デコレータ: Method Decorators

クラスのメソッド宣言の直前に宣言される。
実行時に次の 3 つの引数を持つ関数として呼び出される。

  1. 静的メンバー(static)の場合はクラスのコンストラクター関数、インスタンスメンバー(static以外)の場合はクラスのプロトタイプのいずれか。
  2. プロパティ名
  3. メソッドの Property Descriptor

この例では @enumerable デコレータは greet メソッドに適用され、そのメソッドが列挙可能かどうかを設定している。

// デコレータ
function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  // メソッド・デコレータ使用箇所
  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}
パラメータ・デコレータ: Parameter Decorators

クラスのメソッド宣言内のパラメータの直前に宣言される。
実行時に次の 3 つの引数を持つ関数として呼び出される。

  1. 静的メンバー(static)の場合はクラスのコンストラクター関数、インスタンスメンバー(static以外)の場合はクラスのプロトタイプのいずれか。
  2. プロパティ名
  3. 関数のパラメータインデックス。

この例では @required デコレータは verbose パラメータに適用され、必須の有無を設定している。

import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
// デコレータ
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
class BugReport {
  type = "report";
  title: string;
  constructor(t: string) {
    this.title = t;
  }
  // パラメータ・デコレータ
  print(@required verbose: boolean) {
    if (verbose) {
      return `type: ${this.type}\ntitle: ${this.title}`;
    } else {
     return this.title; 
    }
  }
}
アクセサー・デコレータ: Accessor Decorators

クラスのアクセサ宣言の直前に宣言される。
実行時に次の 3 つの引数を持つ関数として呼び出される。

  1. 静的メンバー(static)の場合はクラスのコンストラクター関数、インスタンスメンバー(static以外)の場合はクラスのプロトタイプのいずれか。
  2. プロパティ名
  3. メソッドの Property Descriptor

この例では @configurableデコレータは x,y アクセサーに適用され、アクセサ内に紐づくプロパティの可不可を設定している。

// デコレータ
function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}
class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }
  @configurable(false)
  get x() {
    return this._x;
  }
  @configurable(false)
  get y() {
    return this._y;
  }
}

コメント

タイトルとURLをコピーしました