TypeScript デコレータ

デコレータ(Decorators)は TypeScript の実験的機能です。

開発者は元のクラスを変更することなく、クラス、メソッド、プロパティ、またはパラメータに追加の機能を追加できます。

デコレータは本質的には関数であり、実行時に呼び出されてターゲットオブジェクトの動作を変更できます。

SVG 図: デコレータの種類 背景 タイトル デコレータの種類と適用位置 中央: クラス @ClassDecorator クラスデコレータ 各方向への矢印 上: プロパティデコレータ プロパティデコレータ @propertyDecorator 右: メソッドデコレータ メソッドデコレータ @methodDecorator 下: パラメータデコレータ パラメータデコレータ @paramDecorator 右下: アクセサデコレータ アクセサデコレータ @getterDecorator デコレータファクトリの説明 デコレータファクトリ(Decorator Factory) デコレータファクトリは、デコレータ関数を返す関数であり、パラメータによって設定を可能にします。 例: function color(code: string) { return function(target) { ... }; } → @color("34") 矢印マーク

デコレータの紹介

デコレータは `@` 記号をシンタックスシュガーとして使用し、クラス、メソッド、アクセサ、プロパティ、またはパラメータに付加できます。

このパターンはフレームワーク開発でよく使用され、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 を変更し、プロパティの設定可能性を制御します
  • プロパティデコレータ:プロパティにメタデータを追加します
  • パラメータデコレータ:メソッドのパラメータをマークまたは検証します
  • デコレータファクトリ:パラメータ化により、より柔軟なデコレータ構成を実現します

その他の拡張