TypeScript デコレータ
デコレータ(Decorators)は TypeScript の実験的機能です。
開発者は元のクラスを変更することなく、クラス、メソッド、プロパティ、またはパラメータに追加の機能を追加できます。
デコレータは本質的には関数であり、実行時に呼び出されてターゲットオブジェクトの動作を変更できます。
SVG 図: デコレータの種類デコレータの紹介
デコレータは `@` 記号をシンタックスシュガーとして使用し、クラス、メソッド、アクセサ、プロパティ、またはパラメータに付加できます。
このパターンはフレームワーク開発でよく使用され、Angular や TypeORM などはデコレータを多用して依存性注入、データ検証などの機能を実現しています。
注意:デコレータは現在実験的機能であり、tsconfig.json で明示的に有効にする必要があります。本番環境で使用する場合は、プロジェクトの実験的機能に対するサポート状況を確認してください。
デコレータを有効にする設定
デコレータを使用する前に、TypeScript 設定ファイル tsconfig.json で関連するコンパイルオプションを有効にする必要があります。
tsconfig.json の設定
"compilerOptions": {
// デコレータ構文を有効にする
"experimentalDecorators": true,
// デコレータのメタデータを有効にする(依存性注入フレームワーク用)
"emitDecoratorMetadata": true
}
}
パラメータの説明:
- experimentalDecorators:デコレータ構文のサポートを有効にします。これはデコレータを使用するための前提条件です。
- emitDecoratorMetadata:コンパイルされた JavaScript にデコレータのメタデータを生成し、依存性注入フレームワークで使用します。
クラスデコレータ
クラスデコレータはクラスのコンストラクタに適用され、クラスの定義を変更したり、追加の処理ロジックを追加したりできます。
クラスデコレータは 1 つのパラメータ、つまりターゲットクラスのコンストラクタを受け取ります。
クラスデコレータの基本的な使い方
// パラメータ target は装飾されたクラスのコンストラクタです
function sealed(target: Function) {
// デコレータが適用されたクラス名を出力する
console.log("デコレータ applied to: " + target.name);
// Object.seal を使用してコンストラクタとプロトタイプをロックする
// 実行時にプロパティを追加または削除できないようにする
Object.seal(target);
Object.seal(target.prototype);
}
// @ 構文を使用してデコレータをクラスに適用する
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// インスタンスを作成してテストする
var person = new Person("サンプル");
console.log("作成: " + person.name);
// 新しいプロパティを追加しようとする(クラスが seal されているためブロックされる)
// person.age = 25; // 静かに失敗する
実行結果:
装饰器 applied to: Person 创建: サンプル
説明:
クラスデコレータはクラス定義時に実行され、通常はクラスの動作の変更、メタデータの追加、または AOP(アスペクト指向プログラミング)の実装に使用されます。
メソッドデコレータ
メソッドデコレータはクラスのメソッドに適用され、メソッドのプロパティ記述子(Property Descriptor)を変更できます。
メソッドデコレータは 3 つのパラメータを受け取ります: ターゲットオブジェクト、プロパティ名、プロパティ記述子です。
メソッドデコレータの基本的な使い方
// デコレータ関数を返す
function enumerable(value: boolean) {
// デコレータ関数を返す。3 つのパラメータを受け取る
return function (
target: any, // 所属クラスのプロトタイプオブジェクト
propertyKey: string, // メソッド名
descriptor: PropertyDescriptor // プロパティ記述子
) {
// プロパティの enumerable 特性を変更する
// false はそのメソッドが列挙不可であることを示す
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
// デコレータを適用し、そのメソッドを列挙不可に設定する
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
var g = new Greeter("World");
// greet メソッドが列挙可能かどうかを確認する
console.log("メソッドが列挙可能: " + g.propertyIsEnumerable("greet"));
// オブジェクトのプロパティを走査する
for (var key in g) {
console.log("プロパティ: " + key);
}
実行結果:
方法可枚举: false
ヒント:PropertyDescriptor には、列挙可能(enumerable)、設定可能(configurable)、書き込み可能(writable)、値(value)などのプロパティが含まれており、必要に応じて変更できます。
アクセサデコレータ
アクセサデコレータはクラスの getter および setter メソッドに適用されます。
メソッドデコレータと同様に、アクセサデコレータもプロパティ記述子を変更できます。
アクセサデコレータの使い方
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// プロパティの configurable 特性を変更する
// false はそのアクセサが再設定または削除できないことを示す
descriptor.configurable = value;
};
}
class Point {
private _x: number = 0;
private _y: number = 0;
// デコレータを使用して getter をロックする
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
set x(value: number) {
this._x = value;
}
set y(value: number) {
this._y = value;
}
}
var point = new Point();
point.x = 10;
point.y = 20;
console.log("座標: (" + point.x + ", " + point.y + ")");
説明:
アクセサデコレータは、同じプロパティの getter と setter の両方には適用できず、どちらか一方のみに適用できます。
プロパティデコレータ
プロパティデコレータはクラスのプロパティ定義に適用されます。
プロパティデコレータは 2 つのパラメータを受け取ります: ターゲットオブジェクトとプロパティ名です。
プロパティデコレータの使い方
function format(formatString: string) {
return function (
target: any, // クラスのプロトタイプオブジェクト
propertyKey: string // プロパティ名
) {
// ターゲットオブジェクトにメタデータを保存する
// propertyKey + "_format" をキー名として使用し、競合を回避する
Object.defineProperty(target, propertyKey + "_format", {
value: formatString,
writable: false,
enumerable: false,
configurable: true
});
};
}
class User {
// プロパティデコレータを適用し、日付形式を指定する
@format("YYYY-MM-DD")
birthDate: string;
constructor(birthDate: string) {
this.birthDate = birthDate;
}
}
var user = new User("1990-01-01");
console.log("生年月日: " + user.birthDate);
// 保存されたメタデータにアクセスする
console.log("日付形式: " + (user as any).birthDate_format);
実行結果:
出生日期: 1990-01-01 日期格式: YYYY-MM-DD
パラメータデコレータ
パラメータデコレータはクラスメソッドのパラメータに適用され、パラメータにメタデータやマークを追加できます。
パラメータデコレータは 3 つのパラメータを受け取ります: ターゲットオブジェクト、メソッド名、関数内のパラメータのインデックスです。
パラメータデコレータの使い方
// パラメータ情報の記録や検証に使用する
function logParameter(
target: any, // クラスのプロトタイプオブジェクト
propertyKey: string, // メソッド名
parameterIndex: number // 関数内のパラメータのインデックス位置(0 から始まる)
) {
console.log("パラメータデコレータ: " + propertyKey +
" 第 " + (parameterIndex + 1) + " 番目のパラメータ");
}
class Greeter {
greeting: string;
constructor(greeting: string) {
this.greeting = greeting;
}
// パラメータの前に @ 構文を使用してデコレータを適用する
greet(@logParameter name: string) {
return this.greeting + ", " + name;
}
}
var greeter = new Greeter("Hello");
greeter.greet("サンプル");
実行結果:
参数装饰器: greet 第 1 个参数
デコレータファクトリ
デコレータファクトリは、デコレータ関数を返す関数です。
デコレータファクトリを使用すると、デコレータを適用する際にカスタムパラメータを渡すことができ、より柔軟な設定が可能になります。
デコレータファクトリで色付きログを実装する
function color(colorCode: string) {
// colorCode は ANSI エスケープシーケンスの色コードです
// 例: 34 = 青、31 = 赤、32 = 緑
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 元のメソッドを保存する
var originalMethod = descriptor.value;
// メソッドを書き換え、色を追加する
descriptor.value = function (...args: any[]) {
// 元のメソッドを呼び出して戻り値を取得する
var result = originalMethod.apply(this, args);
// ターミナル環境の場合、出力に色を追加する
// ANSIエスケープシーケンス形式:\x1b[色コードm 内容 \x1b[0m
return "\x1b[" + colorCode + "m" + result + "\x1b[0m";
};
};
}
class Logger {
// デコレータファクトリを使用し、青色コード34を渡す
@color("34")
log(message: string): string {
return message;
}
@color("31")
error(message: string): string {
return message;
}
@color("32")
success(message: string): string {
return message;
}
}
var logger = new Logger();
console.log(logger.log("これは青いログです"));
console.log(logger.error("これは赤いエラーです"));
console.log(logger.success("これは緑の成功です"));
実行結果:
これは青色ログです(ターミナルには青色で表示されます) これは赤色エラーです(ターミナルには赤色で表示されます) これは緑色の成功です(ターミナルには緑色で表示されます)
説明:デコレータファクトリは実際の開発で最もよく使われる形式であり、デコレータを適用する際にパラメータを渡して構成を実現できます。
デコレータの実行順序
クラスに複数のデコレータがある場合、実行順序は特定の規則に従います。
- デコレータは下から上へ適用されます
- 同じタイプの複数のデコレータは右から左へ実行されます
- パラメータデコレータはメソッドデコレータより先に実行されます
デコレータの実行順序の例
function first() {
console.log("first デコレータ");
return function (target: any) {
console.log("first デコレータ関数");
};
}
function second() {
console.log("second デコレータ");
return function (target: any) {
console.log("second デコレータ関数");
};
}
@first()
@second()
class MyClass {
name: string;
}
var obj = new MyClass();
実行結果:
second 装饰器 first 装饰器 second 装饰器函数 first 装饰器函数
説明:
デコレータ関数はまず定義(console.log)が実行され、その後下から上への順序でデコレータ関数が実行されます。
実際の適用シーン
デコレータは実際のプロジェクトで幅広い適用シーンがあります。
ログ記録
メソッド呼び出しのログを自動的に記録します。
例
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("呼び出しメソッド: " + propertyKey + "、パラメータ: " + JSON.stringify(args));
var result = originalMethod.apply(this, args);
console.log("メソッド戻り値: " + JSON.stringify(result));
return result;
};
}
class MathService {
@log
add(a: number, b: number): number {
return a + b;
}
@log
multiply(a: number, b: number): number {
return a * b;
}
}
var math = new MathService();
console.log("計算結果: " + math.add(5, 3));
実行結果:
调用方法: add,参数: [5,3] 方法返回: 8 计算结果: 8
権限検証
メソッドレベルの権限チェックを実装します。
例
var currentUser = { role: "admin" };
// 権限デコレータ
function requireRole(role: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
if (currentUser.role !== role) {
console.log("権限が不足しているため実行できません " + propertyKey);
return null;
}
return originalMethod.apply(this, args);
};
};
}
class AdminService {
@requireRole("admin")
deleteUser(id: number): string {
return "ユーザー削除 " + id + " 成功";
}
@requireRole("admin")
viewUser(id: number): string {
return "ユーザー表示 " + id;
}
}
var admin = new AdminService();
console.log(admin.viewUser(1));
console.log(admin.deleteUser(1));
// 一般ユーザーをシミュレートする
currentUser = { role: "user" };
console.log(admin.deleteUser(2));
実行結果:
查看用户 1 删除用户 1 成功 权限不足,无法执行 deleteUser
注意事項
- 実験的機能:デコレータは ECMAScript の_stage 3_プロポーザルであり、現在も実験的な機能です
- コンパイルオプション:experimentalDecorators を有効にする必要があります
- 型定義:完全な型サポートを得るには、より新しいバージョンの TypeScript が必要です
- デバッグ時の注意:デコレータはコンパイル時に実行されるため、一部のデバッグツールではソースコードの位置を正しくマッピングできない場合があります
提案:プロジェクトでデコレータを使用する場合は、専用のデコレータツールクラスや関数ライブラリを作成し、デコレータの定義と使用を統一的に管理することをお勧めします。
まとめ
TypeScript デコレータは強力なメタプログラミング能力を提供します。
- クラスデコレータ:クラス自体を変更し、メタデータの追加やクラスのロックなどに使用できます
- メソッドデコレータ:メソッドの属性を変更し、ログや検証などに使用できます
- アクセサデコレータ:getter/setter を変更し、プロパティの設定可能性を制御します
- プロパティデコレータ:プロパティにメタデータを追加します
- パラメータデコレータ:メソッドのパラメータをマークまたは検証します
- デコレータファクトリ:パラメータ化により、より柔軟なデコレータ構成を実現します
その他の拡張