[TypeScript]デコレータの活用例と注意点！[decorator]

## 初めに
前回の記事では、デコレータを用いて、bindメソッドを再利用可能にしました！
デコレータは、コードの冗長性を減少させ、効率的かつ可読性の高いコードを実現できます。

今回は、デコレータの具体的な活用例や注意点を紹介していきます！

# 1. デコレータの応用例
### 1.1 自作ログ出力デコレータ
このデコレータはメソッド呼び出し時に、自作のログを出力できるようにします！
この例では、メソッドが呼び出される前と後にログを表示します。
これにより、メソッドの呼び出し時の引数や結果をトラッキングしやすくなります！

```typescript
function logMethodCall(_target: any, key: string, descriptor: PropertyDescriptor) {
  // 元のメソッドを保持
  const originalMethod = descriptor.value;

  // ディスクリプタのvalueプロパティを新しい関数に上書き
  descriptor.value = function (...args: any[]) {
    // メソッド呼び出し前のログ
    console.log(`Calling method: ${key} with arguments ${JSON.stringify(args)}`);
    
    // オリジナルのメソッドを呼び出し
    const result = originalMethod.apply(this, args);

    // メソッド呼び出し後のログ
    console.log(`Method ${key} execution completed. Result: ${JSON.stringify(result)}`);
    
    // 結果を返す
    return result;
  };
}

class Example {
  // デコレータ追加！
  @logMethodCall
  public someMethod(test: string) {
    // メソッドの処理
    console.log("メソッドの処理");
    return "result: " + test;
  }
}

// インスタンス化
const ex = new Example();
// メソッド呼び出し
ex.someMethod("テスト");
```
![スクリーンショット_20231207_104427.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570986/316c4266-291f-d0f0-df5a-98b59049b4a8.png)

### 1.2 実行時間計測デコレータ
このデコレータはメソッドの実行時間を計測できるようにします！
この例では、console.timeとconsole.timeEndを使用して、メソッドの開始から終了までの時間を計測し、ログとして表示します。

```typescript
function timingDecorator(_target: any, key: string, descriptor: PropertyDescriptor) {
  // 元のメソッドを保持
  const originalMethod = descriptor.value;

  // ディスクリプタのvalueプロパティを新しい関数に上書き
  descriptor.value = async function (...args: any[]) {
    // メソッドの実行時間計測開始
    console.time(`Execution time for method: ${key}`);
    
    // オリジナルのメソッドを呼び出し
    const result = await originalMethod.apply(this, args);

    // メソッドの実行時間計測終了
    console.timeEnd(`Execution time for method: ${key}`);
    
    // 結果を返す
    return result;
  };
}

// SLEEP関数
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

class Example {
  // デコレータ追加！
  @timingDecorator
  public  async someMethod() {
    // メソッドの処理
    console.log("メソッド開始")
    await sleep(3000);
    console.log("3秒経過、メソッド終了");
  }
}

// インスタンス化
const ex = new Example();
// メソッド呼び出し
ex.someMethod();
```
![スクリーンショット_20231207_105747.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570986/67f51468-5271-fc0a-3f5c-5c83393ac7fa.png)

### 1.3 エラー追加デコレータ
このデコレータは、メソッドに独自のエラーを追加できるようにします！
この例では、メソッドの引数が0以上なのか、数字なのかを判定し、違った場合はエラーを投げます。

```typescript
function nonNegativeNumber(_target: any, key: string, descriptor: PropertyDescriptor) {
  // 元のメソッドを保持
  const originalMethod = descriptor.value;

  // ディスクリプタのvalueプロパティを新しい関数に上書き
  descriptor.value = function (...args: any[]) {
    // 引数が数字でない場合、または0未満の場合はエラーを投げる
    if (args.some(arg => typeof arg !== 'number' || arg < 0)) {
      throw new Error(`Arguments must be non-negative numbers for method ${key}.`);
    }

    // オリジナルのメソッドを呼び出し
    const result = originalMethod.apply(this, args);

    // 結果を返す
    return result;
  };
}

class Example {
  // デコレータ追加！
  @nonNegativeNumber
  public calculateSum(a: number, b: number): number {
    // メソッドの処理
    return a + b;
  }
}

// インスタンス化
const ex = new Example();
// メソッド呼び出し
ex.calculateSum(1,-2);
```
![スクリーンショット_20231207_110125.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570986/2173fc89-063a-9802-02bc-f239ec7bee09.png)


# 2. デコレータの注意点

### 2.1 デコレータの順序

デコレータは順序が重要です。
結果に示す通り、デコレータは上から下へと順番に実行されます！

```typescript
function firstDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  // 先に実行されるデコレータの処理
  console.log('First decorator executed.');
}

function secondDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  // 後に実行されるデコレータの処理
  console.log('Second decorator executed.');
}

class Example {
  @firstDecorator
  @secondDecorator
  public someMethod() {
    // メソッドの処理
  }
}
```
![スクリーンショット_20231207_110958.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3570986/82db23ec-37e2-5c3a-48a8-5ef2101d4360.png)

### 2.2 クラスメンバへのデコレータの制約

デコレータはクラスメソッドやプロパティに対して使用できますが、クラス自体に直接適用することはできません。
この例では、@myDecoratorがクラスに適用されているためエラーになります!


```typescript
// これはエラーになる例
@myDecorator
class Example {
  @firstDecorator
  public someMethod() {
    // メソッドの処理
  }
}
```

# まとめ
デコレータは非常に柔軟で強力な機能を提供してくれます！
しかし、適切な理解がないと紹介した注意点によって、想定と違う挙動を起こすことがあるので、しっかりと注意していきましょう！

また、今回紹介した以外のデコレータの活用方法も沢山あるので、調べてみてください！