TypeScriptデコレータを使い方で完全攻略 メタプログラミング設計の要点を整理
2025年12月21日
TypeScript のデコレータは、クラス、メソッド、プロパティなどのメタデータを宣言的に変更・拡張できる強力な機能です。この記事では、デコレータの基礎から実践的な活用方法まで、メタプログラミングの可能性を探ります。
デコレータとは:メタプログラミングの基礎
デコレータは、クラスやそのメンバーの動作を変更・拡張するための特別な宣言です。Java や Python のアノテーションに似た機能で、コードの振る舞いを宣言的に定義できます。
typescript// デコレータの基本的な例
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(
`メソッド ${propertyKey} が呼び出されました`
);
return originalMethod.apply(this, args);
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number) {
return a + b;
}
}
デコレータの種類と基本的な使い方
クラスデコレータ
クラス全体の振る舞いを変更します:
typescriptfunction singleton<T extends { new (...args: any[]): {} }>(
constructor: T
) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) {
return instance;
}
super(...args);
instance = this;
}
};
}
@singleton
class ConfigService {
private config: Record<string, any> = {};
setConfig(key: string, value: any) {
this.config[key] = value;
}
}
メソッドデコレータ
メソッドの振る舞いを拡張または変更します:
typescriptfunction measureTime(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
const result = await originalMethod.apply(this, args);
const end = performance.now();
console.log(`実行時間: ${end - start}ms`);
return result;
};
return descriptor;
}
class ApiService {
@measureTime
async fetchData() {
// データ取得の処理
}
}
プロパティデコレータ
プロパティの定義を変更または拡張します:
typescriptfunction required(target: any, propertyKey: string) {
let value: any;
const getter = function () {
return value;
};
const setter = function (newVal: any) {
if (newVal === undefined || newVal === null) {
throw new Error(`${propertyKey}は必須です`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
}
class User {
@required
name: string;
}
パラメータデコレータ
メソッドパラメータに関する情報を提供します:
typescriptfunction validate(
target: any,
propertyKey: string,
parameterIndex: number
) {
const existingValidators =
Reflect.getMetadata(
'validators',
target,
propertyKey
) || [];
existingValidators.push(parameterIndex);
Reflect.defineMetadata(
'validators',
existingValidators,
target,
propertyKey
);
}
class UserService {
createUser(
@validate name: string,
@validate email: string
) {
// ユーザー作成処理
}
}
アクセサデコレータ
ゲッターやセッターの振る舞いを変更します:
typescriptfunction enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
return descriptor;
};
}
class Person {
private _name: string;
@enumerable(false)
get name() {
return this._name;
}
}
デコレータファクトリの活用
デコレータファクトリを使用すると、パラメータ化されたデコレータを作成できます:
typescriptfunction timeout(milliseconds: number) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error('タイムアウトしました'));
}, milliseconds);
Promise.resolve(originalMethod.apply(this, args))
.then((result) => {
clearTimeout(timeoutId);
resolve(result);
})
.catch(reject);
});
};
return descriptor;
};
}
class ApiClient {
@timeout(5000)
async fetchData() {
// データ取得処理
}
}
実践的なデコレータパターン
バリデーションデコレータ
typescriptfunction minLength(min: number) {
return function (target: any, propertyKey: string) {
let value: string;
const getter = function () {
return value;
};
const setter = function (newVal: string) {
if (newVal.length < min) {
throw new Error(
`${propertyKey}は${min}文字以上である必要があります`
);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}
class User {
@minLength(3)
username: string;
}
ログ出力デコレータ
typescriptfunction logMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(
`[${new Date().toISOString()}] ${propertyKey}が呼び出されました`
);
console.log('引数:', args);
const result = originalMethod.apply(this, args);
console.log('戻り値:', result);
return result;
};
return descriptor;
}
class OrderService {
@logMethod
createOrder(userId: string, items: string[]) {
// 注文作成処理
}
}
キャッシュデコレータ
typescriptfunction memoize(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new Map();
descriptor.value = function (...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
return descriptor;
}
class MathService {
@memoize
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
認可デコレータ
typescriptfunction authorize(roles: string[]) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const currentUser = getCurrentUser(); // 現在のユーザー情報を取得
if (
!roles.some((role) =>
currentUser.roles.includes(role)
)
) {
throw new Error('権限がありません');
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class AdminPanel {
@authorize(['admin'])
deleteUser(userId: string) {
// ユーザー削除処理
}
}
エラーハンドリングデコレータ
typescriptfunction handleErrors(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
console.error(
`エラーが発生しました: ${error.message}`
);
// エラー通知やログ記録などの処理
throw error;
}
};
return descriptor;
}
class DataService {
@handleErrors
async fetchUserData(userId: string) {
// データ取得処理
}
}
デコレータの合成とチェーン
複数のデコレータを組み合わせて使用できます:
typescriptfunction first() {
console.log('first(): factory evaluated');
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('first(): called');
};
}
function second() {
console.log('second(): factory evaluated');
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('second(): called');
};
}
class Example {
@first()
@second()
method() {}
}
デコレータのテスト手法
デコレータのテストは、振る舞いの検証に焦点を当てます:
typescriptdescribe('LogDecorator', () => {
it('should log method calls', () => {
const consoleSpy = jest.spyOn(console, 'log');
class TestClass {
@logMethod
testMethod(arg: string) {
return `Result: ${arg}`;
}
}
const instance = new TestClass();
instance.testMethod('test');
expect(consoleSpy).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('testMethod'),
expect.any(Array)
);
});
});
パフォーマンスとデバッグ
デコレータのパフォーマンスを最適化するためのベストプラクティス:
- キャッシュの活用
- 不要なメタデータの最小化
- 非同期処理の適切な管理
- メモリリークの防止
typescriptfunction optimizedDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new WeakMap();
descriptor.value = function (...args: any[]) {
if (!cache.has(this)) {
cache.set(this, new Map());
}
const instanceCache = cache.get(this);
const key = JSON.stringify(args);
if (instanceCache.has(key)) {
return instanceCache.get(key);
}
const result = originalMethod.apply(this, args);
instanceCache.set(key, result);
return result;
};
return descriptor;
}
デコレータのベストプラクティス
- 単一責任の原則を守る
- デコレータの再利用性を高める
- 適切なエラーハンドリング
- 型安全性の確保
- テスト容易性の考慮
typescript// 良い例:単一責任の原則を守ったデコレータ
function validate(schema: any) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const validationResult = schema.validate(args[0]);
if (validationResult.error) {
throw new ValidationError(validationResult.error);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// 悪い例:複数の責任を持つデコレータ
function validateAndLog(schema: any) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// バリデーション
const validationResult = schema.validate(args[0]);
if (validationResult.error) {
throw new ValidationError(validationResult.error);
}
// ログ出力
console.log(
`Method ${propertyKey} called with args:`,
args
);
// キャッシュ
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
return descriptor;
};
}
まとめ:効果的なデコレータ設計のポイント
- 明確な目的: 各デコレータは明確な単一の目的を持つべき
- 再利用性: 汎用的に使えるよう設計する
- 型安全性: TypeScript の型システムを最大限活用する
- パフォーマンス: 必要な場合のみメタデータを使用し、キャッシュを活用する
- テスト容易性: デコレータの振る舞いを単体でテストできるようにする
- エラーハンドリング: 適切なエラー処理と例外処理を実装する
- ドキュメント: デコレータの使用方法と制限事項を明確に文書化する
デコレータは、TypeScript の強力なメタプログラミング機能の一つです。適切に使用することで、コードの可読性、保守性、再利用性を大きく向上させることができます。
関連リンク
著書
article2025年12月31日TypeScriptでUIコンポーネントを設計する 型から考えるPropsと責務分割
article2025年12月30日TypeScriptの型安全を壊すNGコードを概要でまとめる 回避策10選で負債を減らす
article2025年12月29日SvelteとTypeScriptのユースケース 型安全な開発スタイルを整理する
article2025年12月29日TypeScriptで環境変数を型安全に管理する使い方 envスキーマ設計と運用の基本
article2025年12月29日TypeScriptの型定義ファイルdtsを自作する使い方 破綻しない設計の基本
article2025年12月28日TypeScriptでResult型の使い方を整理する Neverthrowで型安全なエラーハンドリング
article2025年12月31日TypeScriptでUIコンポーネントを設計する 型から考えるPropsと責務分割
article2025年12月30日TypeScriptの型安全を壊すNGコードを概要でまとめる 回避策10選で負債を減らす
article2025年12月29日SvelteとTypeScriptのユースケース 型安全な開発スタイルを整理する
article2025年12月29日TypeScriptで環境変数を型安全に管理する使い方 envスキーマ設計と運用の基本
article2025年12月29日TypeScriptの型定義ファイルdtsを自作する使い方 破綻しない設計の基本
article2025年12月28日TypeScriptでResult型の使い方を整理する Neverthrowで型安全なエラーハンドリング
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
