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でデコレータをなんとなく触っていて、よくわかっていなかったので、少しずつ紐解いていけてる感じです。