TypeScript - デコレータ

こんにちは、未来のプログラマーたち!今日は、TypeScriptのデコレータの世界に興味深く飛び込んでみましょう。プログラミングが初めての方でも心配しないでください。私は年々多くの生徒たちに指導してきた経験を活かして、ステップバイステップでこの概念を説明します。それでは、始めましょう!

TypeScript - Decorators

デコレータとは?

本題に入る前に、まずデコレータとは何かを理解しましょう。あなたが普通のカップケーキを持っているとします。デコレータは、そのカップケーキにスプリンクル、クリーム、そしてチェリーをのせて特別にするものです。TypeScriptでは、デコレータがクラス、メソッド、プロパティ、パラメータに追加機能を提供します。

TypeScriptでのデコレータの使用

TypeScriptでデコレータを使用するには、tsconfig.jsonファイルでそれを有効にする必要があります。これは、カップケーキを焼く前にオーブンを点火するのと同じです!

{
"compilerOptions": {
"experimentalDecorators": true
}
}

デコレータのシンタックス

では、デコレータの書き方を見てみましょう。思ったよりも簡単です!

function simpleDecorator(target: any) {
console.log("私はシンプルなデコレータです!");
}

@simpleDecorator
class MyClass {
// クラスの実装
}

この例では、simpleDecoratorは私たちのMyClassに貼るシールのようなものです。每次MyClassを使用するたびに、「私はシンプルなデコレータです!」とコンソールにログが出ます。

デコレータファクトリー

時々、私たちはカスタマイズ可能なデコレータが必要です。その場合、デコレータファクトリーが役立ちます。これは、指定された仕様に基づいてデコレータを作成する機械のようなものです。

function decoratorFactory(message: string) {
return function (target: any) {
console.log(message);
}
}

@decoratorFactory("こんにちは、カスタムデコレータです!")
class MyClass {
// クラスの実装
}

ここでは、decoratorFactoryはカスタムメッセージをログするデコレータを作成しています。

デコレータの合成

一つのターゲットに複数のデコレータを使用することができます。これは、カップケーキに複数のトッピングを加えるようなものです!

function first() {
console.log("first(): ファクトリ評価");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): 呼び出されました");
};
}

function second() {
console.log("second(): ファクトリ評価");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): 呼び出されました");
};
}

class ExampleClass {
@first()
@second()
method() {}
}

この場合、デコレータは下から上に適用されます:second()からfirst()です。

デコレータの使用理由

デコレータは非常に有用で、以下のような用途に使えます:

  1. コードにメタデータを追加する
  2. クラスやメソッドの動作を変更する
  3. アスペクト指向プログラミングのアスペクトを実装する
  4. 複数のクラスに簡単に適用できる再利用可能なコードを作成する

クラスデコレータ

クラスデコレータはクラスのコンストラクタに適用され、クラス定義を観察、変更、または置き換えるために使用されます。

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

@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

この例では、@sealedデコレータはGreeterクラスが定義された後で変更されるのを防ぎます。

メソッドデコレータ

メソッドデコレータはメソッド定義を変更、観察、または置き換えるために使用されます。

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

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}

ここでは、@enumerable(false)デコレータはgreetメソッドを非列挙可能にします。

アクセッサデコレータ

アクセッサデコレータはアクセッサのプロパティ描述子に適用され、アクセッサの定義を観察、変更、または置き換えるために使用されます。

function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}

class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() { return this._x; }

@configurable(false)
get y() { return this._y; }
}

この例では、@configurable(false)デコレータはxyアクセッサを非設定可能にします。

プロパティデコレータ

プロパティデコレータはプロパティ定義を観察、変更、または置き換えるために使用されます。

function format(formatString: string) {
return function (target: any, propertyKey: string): any {
let value = target[propertyKey];

const getter = function () {
return `${formatString} ${value}`;
};

const setter = function (newVal: string) {
value = newVal;
};

return {
get: getter,
set: setter,
enumerable: true,
configurable: true
};
};
}

class Greeter {
@format("Hello,")
greeting: string;
}

const greeter = new Greeter();
greeter.greeting = "World";
console.log(greeter.greeting); // 出力: "Hello, World"

ここでは、@formatデコレータはgreetingプロパティに接頭辞を追加します。

パラメータデコレータ

パラメータデコレータはパラメータ定義を観察、変更、または置き換えるために使用されます。

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata("required", existingRequiredParameters, target, propertyKey);
}

class Greeter {
greet(@required name: string) {
return "Hello " + name;
}
}

この例では、@requiredデコレータはnameパラメータを必須としてマークします。

デコレータメソッド

以下の表に、異なるデコレータタイプとそのメソッドをまとめます:

デコレータタイプ メソッド
クラスデコレータ declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
メソッドデコレータ declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
アクセッサデコレータ declare type AccessorDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
プロパティデコレータ declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
パラメータデコレータ declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

そして、ここまでがTypeScriptのデコレータの基本です。思い出してください、完璧なカップケーキを焼くのと同じように、デコレータをマスターするには練習が必要です。実験を恐れず、間違ったとしてもそれがプログラマとして成長するためのステップです。お楽しみに!

Credits: Image by storyset