引数contextの働き
ここまで、デコレータ関数の基本形を紹介してきました。ここまでの内容で、紹介していない引数があります。デコレータ関数の第2引数のcontextです。本節では、このcontextを掘り下げていきます。
デコレータが付与されたメソッドの情報が格納されたcontext
これまでのサンプルコードでは、デコレータ関数の第2引数contextのデータ型としてanyを記述していました。実は、このデータ型は正しくはClassMethodDecoratorContextであり、デコレータが付与されたメソッドの情報が格納されたオブジェクトとなっています。具体的には、表1のプロパティが含まれており、それぞれの情報を取得できるようになっています。
| プロパティ名 | データ型 | 内容 |
|---|---|---|
| kind |
文字列"method" |
デコレータが付与された対象 |
| name | string|symbol | メソッド名 |
| static | boolean | staticメソッドかどうか |
| private | boolean | privateメソッドかどうか |
| access | オブジェクト | デコレータが付与されたオブジェクトの内部データにアクセスするためのオブジェクトを提供 |
| addInitializer | メソッド | 初期化処理時の追加処理を設定 |
例えば、リスト6の(1)の部分を、リスト9の(1)のように変更すると、コンソールへの表示内容は「メソッドshowが実行されました。」とそのメソッド名を表示できるようになります。
function logging(originalMethod: any, context: ClassMethodDecoratorContext) {
function replacementMethod(this: any, ...args: any[]) {
console.log(`メソッド${String(context.name)}が実行されました。`); // (1)
const originalReturn = originalMethod.call(this, ...args);
return originalReturn;
}
return replacementMethod;
}
なお、context.nameは、表1のように、symbol型の場合もあります。そのため、表示する場合は、String()を記述して文字列への変換を行っておかないと、TypeScriptのエラーとなるので注意してください。
追加の初期化処理の設定
ここから、さらに、contextオブジェクトのプロパティに関して、いくつか補足していきます。まずは、addInitializer()です。これは、メソッドとなっていますので、その引数に関数を渡すことで、デコレータが付与されたメソッドのクラス本体がnewされる際に、追加の処理を設定することができます。例えば、リスト10のようなコードです。この場合、SelfIntroクラスがnewされる際に、コンソールに「初期化!」と表示されます。
function logging(originalMethod: any, context: any) {
context.addInitializer(function() {
console.log("初期化!");
});
function replacementMethod(this: any, ...args: any[]) {
:
}
return replacementMethod;
}
kindプロパティの役割とデコレータの対象
表1のkindプロパティのデータ型が、「文字列"method"」となっているのを不思議に思った方もいるかもしれません。実は、このkindプロパティは、デコレータが付与された対象を表します。先述のように、デコレータは全てのクラスメンバ、および、クラス本体に付与できます。そして、何に付与されたのかに応じてこのkindプロパティの値は、classやmethodのように変わるようになっています。そして、実は、デコレータが付与された対象に応じて、contextオブジェクトのデータ型も変わってきます。この対応関係をまとめると、表2のようになります。
| 対象 | kindの値 | contextのデータ型 |
|---|---|---|
| クラス本体 | class | ClassDecoratorContext |
| メソッド | method | ClassMethodDecoratorContext |
| ゲッタ | getter | ClassGetterDecoratorContext |
| セッタ | setter | ClassSetterDecoratorContext |
| フィールド | field | ClassFieldDecoratorContext |
| オートアクセサ | accessor | ClassAutoAccessorDecoratorContext |
このことから、例えば、ClassMethodDecoratorContext型のkindプロパティは、methodという値しかあり得ませんので、ユニット型(第4回参照)となっています。
各デコレータ関数の違いはcontextのデータ型
実は、ここまでメソッドに付与するデコレータを題材に、デコレータを紹介してきましたが、その他のデコレータとの違いは、このcontextオブジェクトのデータ型が主な違いとなっています。とはいえ、そのプロパティ構成にはあまり差はありません。また、デコレータ関数の第1引数に関しても、メソッドに付与するデコレータの場合は、originalMethodという名称からわかるように、関数形式(メソッド)が渡されますが、これが、例えば、フィールドの場合は、フィールドに格納された値そのものがわかってきます。
最後に、このような違いを、表3にまとめておきます。
| 対象 | デコレータ関数の第1引数 | contextのプロパティ構成 |
|---|---|---|
| クラス本体 | 関数 | kind、name、addInitializer()のみ |
| メソッド | 関数 | ClassMethodDecoratorContextそのもの |
| ゲッタ | 関数 | ClassMethodDecoratorContextと同じ構成 |
| セッタ | 関数 | ClassMethodDecoratorContextと同じ構成 |
| フィールド | フィールドの値(undefined型) | ClassMethodDecoratorContextと同じ構成 |
| オートアクセサ | ゲッタとセッタが格納されたオブジェクト | ClassMethodDecoratorContextと同じ構成 |
まとめ
TypeScriptのバージョン5.2までに導入された新機能をテーマごとに紹介する本連載の第6回目はいかがでしたでしょうか。
今回は、バージョン5で導入された新しいデコレータを紹介しました。そして、バージョン5.2までの新機能を紹介する本連載は、これで一区切りとなります。
第1回を執筆した段階では、TypeScriptの最新バージョンは5.2でした。そのため、その最新である5.2までの新機能をまとめる連載としたわけですが、回を重ねる間にもTypeScriptはバージョンアップを行い、今回の原稿を執筆している時点では、5.4となっており、5.5がベータリリースとなっています。ますます機能が追加、改善され、便利になっていくものと思います。ある程度、新機能がまとまった段階で、本連載の続きとして紹介したいと思います。乞うご期待!
