はじめに
TypeScriptの本を読んでいると、デコレーターという物をみました。今までデコレーターってわかっているようでわかっていなかったので、ちょっと学んでみようかなぁと思って記事にしました。
Decoratorとは何か??
クラスやクラスのメンバー(メソッド、プロパティなど)に対して追加の機能を動的に付加するためのものらしいです。。うーん、、、ちょっとよくわからない。実際に具体例を交えながら学んでいこうと思います。
ソースコード
クラスデコレーター
クラス全体に対して適用されるデコレーターです。
classDecorator.ts
// biome-ignore lint/complexity/noBannedTypes: <explanation>
function Logger(target: Function) {
console.log(`クラスが作成されました: ${target.name}`);
}
@Logger
class MyClass {
constructor() {
console.log("MyClassのインスタンスが生成されました");
}
}
const myClassInstance = new MyClass();
実行結果
~/develop/typescript_decorator$ ts-node classDecorator.ts
クラスが作成されました: MyClass
MyClassのインスタンスが生成されました
メソッドデコレーター
メソッドに対して適用されるデコレーターです。
methodDecorator.ts
function Log(
// biome-ignore lint/suspicious/noExplicitAny: This is needed to allow any type for the target
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
console.log("111", target, propertyKey, descriptor);
const originalMethod = descriptor.value;
console.log("=======22 ", originalMethod);
// biome-ignore lint/suspicious/noExplicitAny: This is needed to allow any type for the arguments
descriptor.value = function (...args: any[]) {
console.log(`メソッド ${propertyKey} が呼び出されました`);
return originalMethod.apply(this, args);
};
return descriptor;
}
class MyClass {
@Log
myMethod() {
console.log("メソッドの実行中...");
}
}
const myClassInstance = new MyClass();
myClassInstance.myMethod();
実行結果
~/develop/typescript_decorator$ ts-node methodDecorator.ts
111 {} myMethod {
value: [Function: myMethod],
writable: true,
enumerable: false,
configurable: true
}
=======22 [Function: myMethod]
メソッド myMethod が呼び出されました
メソッドの実行中...
アクセサリーデコレーター
getterまたはsetterのアクセサリに対して適用されるデコレーターです。
function Enumerable(isEnumerable: boolean) {
// biome-ignore lint/suspicious/noExplicitAny: This is needed to allow any type for the target
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
descriptor.enumerable = isEnumerable;
};
}
class MyClass {
private _value = 0;
@Enumerable(false)
get value(): number {
return this._value;
}
set value(val: number) {
this._value = val;
}
}
const myClassInstance = new MyClass();
console.log(Object.keys(myClassInstance)); // 'value' は表示されない
プロパティデコレーター
プロパティに対して適用されるデコレーターです。
propertyDecorator.ts
// biome-ignore lint/suspicious/noExplicitAny: This is needed to allow any type for the target
function ReadOnly(target: any, propertyKey: string) {
Object.defineProperty(target, propertyKey, {
writable: false,
});
}
class MyClass {
@ReadOnly
name = "Initial name";
}
const myClassInstance = new MyClass();
try {
myClassInstance.name = "Changed name"; // エラー: nameは読み取り専用です
} catch (error) {
// biome-ignore lint/suspicious/noExplicitAny: This is needed to handle any type of error
console.error((error as any).message);
}
実行結果
~/develop/typescript_decorator$ ts-node propertyDecorator.ts
/Users/kawamurakouji/develop/typescript_decorator/propertyDecorator.ts:10
name = "Initial name";
^
TypeError: Cannot assign to read only property 'name' of object '#<MyClass>'
at new MyClass (/Users/kawamurakouji/develop/typescript_decorator/propertyDecorator.ts:10:6)
at Object.<anonymous> (/Users/kawamurakouji/develop/typescript_decorator/propertyDecorator.ts:13:25)
at Module._compile (node:internal/modules/cjs/loader:1369:14)
at Module.m._compile (/Users/kawamurakouji/.config/yarn/global/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
at Object.require.extensions.<computed> [as .ts] (/Users/kawamurakouji/.config/yarn/global/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:1206:32)
at Function.Module._load (node:internal/modules/cjs/loader:1022:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at phase4 (/Users/kawamurakouji/.config/yarn/global/node_modules/ts-node/src/bin.ts:649:14)
パラメーターデコレーター
メソッドのパラメータに対して適用されるデコレーターです。
parameterDecorator.ts
// biome-ignore lint/suspicious/noExplicitAny: This is needed to allow any type for the target
function LogParameter(target: any, propertyKey: string, parameterIndex: number) {
const metadataKey = `log_${propertyKey}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(parameterIndex);
} else {
target[metadataKey] = [parameterIndex];
}
}
class MyClass {
myMethod(@LogParameter message: string) {
console.log(message);
}
}
const myClassInstance = new MyClass();
myClassInstance.myMethod('Hello, world!');
// 出力: Hello, world!