りゅうじの学習blog

学習したことをアウトプットしていきます。

7/15 TypeScript デコレータ

デコレータ

メタプログラミングに役にたつ

他の開発者が使いやすい道具を提供することに向いている

使うための準備

tsconfig.jsonの設定

"target": "es6"
"experimentalDecorators": true
  • 上記の二つを設定する必要がある

クラスデコレータ

function Logger(constructor: Function) {
  console.log("ログ出力中…");
  console.log(constructor);
}

@Logger
class Person {
  name = "Max";

  constructor() {
    console.log("Personオブジェクトを作成中…");
  }
}

const pers = new Person();

console.log(pers);
  • Loggerというデコレータを定義している
    • 大文字から始めなければいけないわけではないが、大文字から定義されていることが多い
      • Personクラスの前に@Loggerとしてデコレータを定義

重要なこととして、デコレータが呼び出されるのはインスタンス化された時ではなく、@Loggerのタイミングで呼び出されるということ

出力結果

ログ出力中…
app.js:10 class Person {
    constructor() {
        this.name = "Max";
        console.log("Personオブジェクトを作成中…");
    }
}
app.js:15 Personオブジェクトを作成中…
app.js:22 Person {name: 'Max'}
  • ログ出力中とデコレータで呼び出されているコンストラクタ関数が、Personがインスタンス化された際に呼び出される出力(Personオブジェクトを作成中、Person {name: 'Max'})より前に呼び出されている

デコレータファクトリ

デコレータ関数を何かに割り当てるときにデコレータをカスタマイズできるようにするもの

function Logger() {
  return function (constructor: Function) {
    console.log("ログ出力中…");
    console.log(constructor);
  };
}

@Logger()
class Person {
  name = "Max";

  constructor() {
    console.log("Personオブジェクトを作成中…");
  }
}

const pers = new Person();

console.log(pers);
  • Loggerのデコレータの中で無名関数を定義する
    • これで新しい関数を返す関数を定義することができた
  • これをデコレータとして適応するために、Logger()と関数として実行する必要がある

使い方の例

function Logger(logString: string) {
  return function (constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

@Logger("ログ出力中 - PERSON")
class Person {
  name = "Max";

  constructor() {
    console.log("Personオブジェクトを作成中…");
  }
}

const pers = new Person();

console.log(pers);
  • ファクトリ関数を実行したときに、その中の無名関数内で使う値をカスタマイズできるようになった
    • @Logger("ログ出力中 - PERSON")では関数を実行しているのではなく、デコレータ関数を返す関数を実行している

複数のデコレータを実行順序

function Logger(logString: string) {
  console.log("LOGGER ファクトリ");
  return function (constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

function Template(template: string) {
  console.log("TEMPLATE ファクトリ");
  return function (constructor: Function) {
    console.log(template);
  };
}

@Logger("ログ出力中 - PERSON")
@Template("テンプレートを出力中")
class Person {
  name = "Max";

  constructor() {
    console.log("Personオブジェクトを作成中…");
  }
}

const pers = new Person();

console.log(pers);
  • Templateファクトリ関数を追加して、@Templateデコレータを@Loggerデコレータの後に実行した
  • この時の実行順序を確認します
LOGGER ファクトリ
app.js:16 TEMPLATE ファクトリ
app.js:18 テンプレートを出力中
app.js:11 ログ出力中 - PERSON
  • ファクトリ関数としては、基本通りに上から順番に実行されている
  • 注目すべきは、ファクトリ関数の内部の無名関数の実行順序は、テンプレートのほうが先に実行されていることです

デコレータが複数定義されていたら、デコレータの内部の実行順序としては、したから順番に出力されます。

コメント

NestJSでデコレータをなんとなく触っていて、よくわかっていなかったので、少しずつ紐解いていけてる感じです。