💅
あらためてデコレーターを学ぶぞ
はじめに
- NestJS入門してみようとしたら、アットマークから始まる記述を知らなかったので調べたことを簡単にまとめた記事です(デコレーターのことよくわかってなかった)
- デコレーターの仕様は、過去にいろいろと変わって現在に至っているようでしたが、ようやく最近 ECMAScript では Stage 3 になり、TypeScript では今も実験的なサポート experimentalDecorators を使えますが、バージョン 4.8 で TC39 の Decorators を実装することが検討されているようです!
- この記事は、TypeScriptのドキュメントのサンプルコードを動かしてみたり、あらためてNestJSのコード読んだりしてみた感想を書いてみました。
- 最近のデコレーターを追えてない、NestJSを動かしてみようとしたらデコレーターってなんぞや、となったかたの参考になれば幸いです。
ESデコレーターの近況
- まず、ESデコレーターの近況について。少し前ですがステージ3に!
- そして、TypeScriptでは4.8で実装される予定のようです!
デコレーターとは
-
デコレータは、クラス宣言、メソッド、アクセサ、プロパティ、またはパラメータにアタッチできる特別な種類の宣言のことです。
- ざっくりいうと、クラスやメソッドを装飾してくれる機能です。
-
@expression
の形式で記述します。 -
NestJSのコード例で示すと、
@Controller
や@Get
、@Param
などのことです。// NestJSのコード例 @Controller('profiles') export class ProfileController { constructor(private readonly profileService: ProfileService) {} @Get(':username') async getProfile(@User('id') userId: number, @Param('username') username: string): Promise<ProfileRO> { return await this.profileService.findProfile(userId, username); } }
デコレーターファクトリー
- デコレーターは、クラスやメソッドを装飾してくれる機能ですが、その装飾してくれる処理内容を記述するのがデコレーターファクトリーです。
- データファクトリーを使うと、デコレーターによって呼び出される式を返す関数をカスタマイズすることができます。
-
サンプルコード
// デコレーターファクトリー function first() { console.log("first(): factory evaluated"); return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("first(): called"); }; } // デコレーターファクトリー function second() { console.log("second(): factory evaluated"); return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { console.log("second(): called"); }; } class ExampleClass { @first() // デコレーター @second() // デコレーター method() {} } // コンソールへの出力 // first(): factory evaluated // second(): factory evaluated // second(): called // first(): called
デコレーターの種類
デコレーターを宣言できる場所によっていくつか種類があります。
クラスデコレーター
- クラスの宣言前に宣言するデコレーターです。
-
クラスデコレーターの式は、実行時に関数として呼び出され、デコレートされたクラスのコンストラクタが唯一の引数になります。
// クラスデコレーターの式 function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) { return class extends constructor { reportingURL = "http://www..."; }; } @reportableClassDecorator class BugReport { type = "report"; title: string; constructor(t: string) { this.title = t; } } const bug = new BugReport("Needs dark mode"); console.log(bug.title); // Prints "Needs dark mode" console.log(bug.type); // Prints "report" // Note that the decorator _does not_ change the TypeScript type // and so the new property `reportingURL` is not known // to the type system: // @ts-ignore console.log(bug.reportingURL); // Prints "http://www..."
メソッドデコレーター
- メソッドの宣言前に宣言するデコレーターです。
-
メソッドデコレーターの式は、実行時に関数として呼び出されます。
-
次の3つの引数を取ります。
-
target
静的メンバーのクラスのコンストラクター関数、またはインスタンスメンバーのクラスのプロトタイプのいずれか(Either the constructor function of the class for a static member, or the prototype of the class for an instance member.) -
propertyKey
メンバーの名前(The name of the member.) -
descriptor
メンバーのプロパティ記述子、object の property(The Property Descriptor for the member.)
-
-
サンプルコード
// デコレーターファクトリー
function change(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 値を出力してみる
console.log('target =>', target);
console.log('propertyKey =>', propertyKey);
console.log('descriptor =>', descriptor);
// 関数を書き換えてみる例
const addFunc = descriptor.value;
descriptor.value = function () {
const result = addFunc.apply(this);
if (value) {
return 'こんにちは';
}
return result
};
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@change(true)
greet() {
return "Hello, " + this.greeting;
}
}
// 値を出力してみる
console.log('new Greeter("World").greet() =>', new Greeter("World").greet());
- コンソールの出力結果
// console.logの出力結果
target => { greet: [Function (anonymous)] } // 静的メンバーのクラスのコンストラクター関数、またはインスタンスメンバーのクラスのプロトタイプのいずれか
propertyKey => greet
descriptor => {
value: [Function (anonymous)], // デコレーとした関数そのものが入ってる
writable: true,
enumerable: true,
configurable: true
}
new Greeter("World").greet() => こんにちは
アクセサデコレーター
- アクセサ宣言の直前に宣言されるデコレーターです。
プロパティーデコレーター
- プロパティ宣言の直前に宣言されるデコレーターです。
パラメータデコレーター
- パラメータ宣言の直前に宣言されるデコレーターです。
NestJSとデコレーター
- NestJSは、デコレーターの言語機能を中心にして作られていて、デコレーターがたくさん使われています。デコレーターのドキュメントをひととおり眺めてみたので、あらためてNestJSのコードを簡単に見てみました。
- コントローラーの一例。AppControllerクラスの上にControllerデコレータが記述されています。デコレーターを使ってクラスを装飾することでコントローラーとして使えるようにしています。
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
- NestJSの
@Controller
のデコレーターのコードのコードを探してみた
- NestJSの
@Get
のデコレーターのコードを探してみた
おわりに
- デコレーターについて調べたことを簡単にまとめてみました。
- NestJSに限らず、デコレーターが採用されているライブラリはたくさんあると思うので、ちゃんと使い方を理解したら、便利そうで良さそうな機能だと思いました。
- 過去、色々仕様が変わったりなどで歴史的な経緯がありそうでしたが、最近ではステージ3になりTypeScript4.8にも入りそうなので、もう少しちゃんと使い方をチェックしておきたいと思いました。
参考
デコレーターを調べてて読んだリンク集です!勉強になりました!ありがとうございました!
GitHubで編集を提案
Discussion