404 Not Found

404 Not Found


nginx

TypeScriptのデコレータ

デコレータは実験的な構文であり、@decorator構文を用いて、クラス、メソッド、プロパティにメタデータを追加したり、それらの挙動を変更したりします。NestJSやAngularなどのフレームワークで広く利用されています。

1. デコレータの概要

(1) デコレーターとは何か?

デコレータとは関数のことであり、引数として対象(クラス、メソッド、またはプロパティ)を受け取り、その対象の挙動を変更したり強化したりすることができます:

TYPESCRIPT
// Decorator Functions
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Call ${key},Parameters:${args}`);
    return original.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

let calc = new Calculator();
calc.add(1, 2);
// Output:Call add,Parameters:1,2

(2) デコレータのサポートを有効にする

デコレータは実験的な機能であり、tsconfig.jsonで有効にする必要があります:

JSON
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

(3) 5種類のデコレーター

タイプ 変更後のターゲット パラメータ
クラスデコレータ クラス定義 コンストラクタ
メソッドデコレータ クラスメソッド ターゲット、メソッド名、記述子
プロパティ・デコレータ クラスのプロパティ ターゲット、プロパティ名
パラメータデコレータ 関数のパラメータ ターゲット、メソッド名、パラメータのインデックス
アクセサ・デコレータ ゲッター/セッター ターゲット、アクセサ名、記述子

2. クラス・デコレータ

クラスデコレータはコンストラクタを引数として受け取り、クラスの定義を変更または置き換えることができます:

(1) 基本的な使い方

TYPESCRIPT
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Config {
  host: string = "localhost";
  port: number = 3000;
}

// Config Has been sealed——New properties cannot be added

(2) デコレータファクトリ

パラメータが必要な場合は、ファクトリ関数を使用してデコレータを返します:

TYPESCRIPT
function className(prefix: string) {
  return function (constructor: Function) {
    constructor.prototype._displayName = `${prefix}_${constructor.name}`;
  };
}

@className("App")
class UserService {}
// UserService.prototype._displayName = "App_UserService"

(3) コンストラクタのオーバーライド

TYPESCRIPT
function logged<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      console.log(`Create ${constructor.name},Parameters:${args}`);
      super(...args);
    }
  };
}

@logged
class User {
  constructor(public name: string, public age: number) {}
}

let user = new User("Charlie", 20);
// Output:Create User,Parameters:Charlie,20

▶ 例:登録デコレータ—クラスの自動収集

TYPESCRIPT
const registry: Map<string, any> = new Map();

function Register(name: string) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    registry.set(name, constructor);
    return constructor;
  };
}

@register("user")
class UserService {
  getUser(id: number) { return { id, name: "User" + id }; }
}

@register("product")
class ProductService {
  getProduct(id: number) { return { id, title: "Products" + id }; }
}

// Get a Service by Name
function getService(name: string) {
  let Service = registry.get(name);
  if (!Service) throw new Error(`Unregistered Services:${name}`);
  return new Service();
}

let user = (getService("user") as UserService).getUser(1);
let product = (getService("product") as ProductService).getProduct(2);

console.log(user.name);     // "User1"
console.log(product.title); // "Products2"

3. メソッドデコレータ

メソッドデコレータは、メソッドの定義を監視、変更、または置換することができます:

(1) 基本的な使い方

TYPESCRIPT
function enumerable(value: boolean) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Person {
  constructor(public name: string) {}

  @enumerable(false)
  getFullName(): string {
    return this.name;
  }
}

(2) メソッド実行ログ

TYPESCRIPT
function measure(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = original.apply(this, args);
    const end = performance.now();
    console.log(`${key} Execution Time:${(end - start).toFixed(2)}ms`);
    return result;
  };

  return descriptor;
}

class DataProcessor {
  @measure
  processLargeArray(data: number[]): number {
    return data.reduce((sum, n) => sum + n, 0);
  }
}

let processor = new DataProcessor();
processor.processLargeArray(Array.from({ length: 1000000 }, (_, i) => i));
// Output:processLargeArray Execution Time:XX.XXms

(3) ジッター対策手法

TYPESCRIPT
function debounce(delay: number) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    let timer: any;

    descriptor.value = function (...args: any[]) {
      clearTimeout(timer);
      timer = setTimeout(() => original.apply(this, args), delay);
    };

    return descriptor;
  };
}

class SearchBox {
  @debounce(300)
  search(query: string): void {
    console.log(`Search:${query}`);
  }
}

let box = new SearchBox();
box.search("T");     // 300ms Then, if there is no new input, execute it
box.search("Ty");
box.search("Typ");
box.search("TypeScript");  // This will be executed only once

4. プロパティ・デコレータとパラメータ・デコレータ

(1) プロパティ・デコレータ

TYPESCRIPT
function format(formatStr: string) {
  return function (target: any, key: string) {
    // Store formatting information in metadata
    Reflect.defineMetadata("format", formatStr, target, key);
  };
}

class User {
  @format("YYYY-MM-DD")
  birthday: string = "2000-01-15";

  @format("HH:mm:ss")
  loginTime: string = "09:30:00";
}

(2) パラメータデコレータ

TYPESCRIPT
function required(target: any, key: string, index: number) {
  const existing: number[] = Reflect.getMetadata("required", target, key) || [];
  existing.push(index);
  Reflect.defineMetadata("required", existing, target, key);
}

class UserService {
  createUser(@required name: string, @required email: string, age?: number) {
    // Parameter validation is handled by the framework at runtime.
  }
}

5. デコレータの組み合わせ

複数のデコレータを同時に使用することができます。これらは下から上へと順に実行されます(対象に最も近いものが最初に実行されます):

TYPESCRIPT
function log1(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log("log1 Applications");
}

function log2(target: any, key: string, descriptor: PropertyDescriptor) {
  console.log("log2 Applications");
}

class Example {
  @log1    // The Second Application(Outer layer)
  @log2    // The First Application(Inner layer,Approach Methods)
  method() {}
}
// Output:log2 Applications → log1 Applications

(1) 一般的な組み合わせパターン

TYPESCRIPT
class ApiController {
  @logged
  @measure
  @debounce(100)
  async fetchData(url: string): Promise<any> {
    // First debounce → then measure → then logged(Bottom-up application)
  }
}

❓ よくある質問

Q デコレータは安定した機能ですか?
A 現在は実験的な機能(Stage 3 提案)です。TypeScript 5.0 以降では、新しい Stage 3 デコレータ構文がサポートされています(experimentalDecorators は不要になりました)が、旧構文も引き続き使用可能です。NestJS や Angular などのフレームワークでは、現在も旧構文が使用されています。ご利用のフレームワークが提供するガイドラインに従うことをお勧めします。
Q デコレータは関数と一緒に使用できますか?
A いいえ。デコレータはクラスおよびクラスのメンバー(メソッド、プロパティ、パラメータ)でのみ使用できます。通常の関数はデコレータをサポートしていません。これはJavaScript仕様案の設計上の制限です。関数を拡張する必要がある場合は、高階関数(ラッパー)パターンを使用してください。
Q デコレータはパフォーマンスにどのような影響を与えますか?
A デコレータはクラスが定義された際に一度だけ実行される(呼び出しのたびに実行されるわけではない)ため、オーバーヘッドはごくわずかです。ラップされた関数を変更するメソッドデコレータの場合、呼び出しのたびにわずかなオーバーヘッド(追加の関数呼び出し)が発生しますが、通常は無視できる程度です。パフォーマンスが重要なシナリオでは、ホットパスに対して複数のデコレータを連鎖して使用することは避けてください。
Q デコレータを使わずに同じ効果を得ることはできますか?
A はい。デコレータは本質的に構文上の糖衣であり、@log method()method = log(method)と同等です。デコレータを使わなくても、高階関数、ミックスインパターン、AOPライブラリ、その他の手法を用いて同じ結果を得ることができます。デコレータの利点は、宣言的であり、直感的で、よりクリーンなコードが得られることです。

📖 まとめ

📝 練習問題

  1. 基本演習(難易度 ⭐):メソッドをオーバーライド不可にする @readonly メソッドデコレータを作成してください(writable: false)。これをクラスのメソッドに適用し、サブクラスでそのメソッドをオーバーライドしてみて、その結果を確認してください。
  2. 上級問題(難易度 ⭐⭐):デコレータされたメソッドが呼び出された際に非推奨の警告を出力する @deprecated(message) デコレータファクトリを作成してください。ヒント:メソッドラッパー関数に console.warn を追加してください。
  3. 課題(難易度:⭐⭐⭐):クラスデコレータを使用して、簡単な依存性注入を実装してください。サービスクラスに @Injectable() を、依存プロパティに @Inject(Service) を指定すると、Container.resolve(TargetClass) が自動的に依存関係を解決して注入します。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%