1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptのデコレータ、なんとなく使ってない?仕組みから自作・応用まで本質的に理解する

Posted at

こんにちは。TypeScriptやNestJSを日常的に使っているエンジニアです。

NestJSやTypeORM、クリーンアーキテクチャ系のライブラリを触っていると、やたら出てくるこの記法:

@Controller('users')
export class UserController {
  @Get(':id')
  getUser(@Param('id') id: string) {
    return `User ID is ${id}`;
  }
}

この @Get@Param何をしてるのか説明できますか?

「なんとなく使ってるけど仕組みはよく知らない」
「NestJSで当たり前に使ってるけど、自分で作ったことはない」
――そんな方のために、TypeScriptのデコレータを基礎から応用まで本気で理解するための記事を書いてみました。


✅ デコレータとは?一言で言うと…

クラスやメソッド、プロパティなどに「機能を後付け」できる構文

JavaやPythonのアノテーションを触ったことがある方なら「ああ、あれね」と思ってもらえればOKです。

例えば、こんな風に使います:

@Log
class UserService {}

ここで @Log は、クラス定義に割り込む関数です。定義時点でこの関数が呼ばれ、何らかの処理をします。


🎯 デコレータはどこに使えるの?

TypeScriptでは、以下の場所にデコレータを使えます:

種類 対象
クラス クラス定義 @Controller()
メソッド クラスの関数 @Get(), @Post()
プロパティ クラスのフィールド @Column()
パラメータ メソッドの引数 @Param(), @Body()

⚙️ 実際どう動くの?(基礎編)

🔧 クラスデコレータの例

function Log(constructor: Function) {
  console.log(`クラス ${constructor.name} が定義されました`);
}

@Log
class UserService {}

この場合、UserService クラスが読み込まれるタイミングで、Log() 関数が呼び出されます。要するにただの関数コールなんです。


🛠 自作デコレータでメソッドにログ出してみる

繰り返し使う処理は、デコレータで共通化できます。よくあるのがログ出力系。

✨ ログ付きメソッドデコレータ

function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`[LOG] ${propertyKey} called with:`, args);
    const result = original.apply(this, args);
    console.log(`[LOG] ${propertyKey} returned:`, result);
    return result;
  };
}

✅ 使ってみる

class UserService {
  @LogMethod
  getUser(id: number) {
    return { id, name: 'Taro' };
  }
}

const svc = new UserService();
svc.getUser(42);

✅ 出力

[LOG] getUser called with: [ 42 ]
[LOG] getUser returned: { id: 42, name: 'Taro' }

ログ機能がきれいに分離できました。何度も使い回せるように抽象化できるのがポイントです。


🔍 応用編:reflect-metadata を使って型情報を扱う

NestJSやTypeORMは、デコレータに型情報を持たせて処理しています。TypeScript単体ではこれはできないので、reflect-metadata というライブラリを使います。

📦 セットアップ

npm install reflect-metadata

tsconfig.json に追記:

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

ファイルの先頭で import:

import 'reflect-metadata';

🎯 型情報を取得するデコレータの例

function LogType(target: any, key: string) {
  const type = Reflect.getMetadata('design:type', target, key);
  console.log(`Property "${key}" has type: ${type.name}`);
}

✅ 使用例

class Sample {
  @LogType
  name: string;

  @LogType
  age: number;
}

✅ 出力

Property "name" has type: String
Property "age" has type: Number

こうやってプロパティの型情報にアクセスできるので、DI、バリデーション、スキーマ生成などに応用できます。NestJSはこれをベースに動いてます。


📚 NestJSでのデコレータ活用の裏側(簡単に)

NestJSでは、以下のようなデコレータが大量に使われています:

@Controller('users')
export class UserController {
  @Get(':id')
  getUser(@Param('id') id: string) {}
}

これらはすべてメタデータとして情報を集約して、内部でルーティングや依存性注入の設定に使っているわけです。まさにデコレータの恩恵です。


✅ おさらい:デコレータの要点まとめ

ポイント 内容
何? クラスや関数に「機能を後から追加」する仕組み
実態 ただの関数。コンパイル時に呼び出される
使用場所 クラス、メソッド、プロパティ、引数
応用技術 reflect-metadata を使えば型情報も扱える
実例 NestJS、TypeORM、class-validator などで活用

🧪 実践のすすめ

TypeScriptでデコレータを使いこなせるようになると、

  • 共通処理の切り出し
  • ライブラリの中身の理解
  • フレームワークの構築

など、ワンランク上のコード設計ができるようになります。慣れるまでは魔法っぽいですが、「ただの関数」だと気づくと一気に楽になりますよ。


📎 参考リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?