読み込み中...

TypeScriptでextendsを使った10の驚きの手法を紹介

TypeScriptでのextendsを理解し、驚きの使い方10選を探るイメージ TypeScript
この記事は約28分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptは、大規模なアプリケーション開発や、安全なコーディングを強化するために生まれたJavaScriptのスーパーセットです。

TypeScriptの特長の一つである「extends」について、今回は詳しく解説いたします。

この記事を通して、TypeScript初心者でも「extends」の驚きの使い方や応用方法を簡単に理解し、実際のコーディングに活かせるようになります。

●TypeScriptとextendsの基本

TypeScriptは、単にJavaScriptを拡張するだけでなく、安全でスケーラブルなコードベースの構築をサポートするために、静的型付けやクラスベースのオブジェクト指向プログラミングを可能にする強力な機能を提供します。

この記事では、そんなTypeScriptの中核を成す概念である「継承」を解き明かします。

extends キーワードを用いることで具体化されるこの機能は、コードの再利用や拡張が容易になるため、開発効率と保守性を大幅に向上させることができるのです。

それでは、具体的な使い方を見ていきましょう。

○TypeScriptとは?

TypeScriptは、Microsoftが開発したJavaScriptのスーパーセットとして知られています。

JavaScriptのコードはそのままTypeScriptとして動作するため、JavaScriptの知識がある方はスムーズに移行できます。

しかし、TypeScriptは静的型付けを提供することで、コンパイル時に型のエラーを検出することができる最大の利点があります。

これにより、ランタイム時のエラーを大幅に削減し、品質の高いコードを書くことが可能となります。

○extendsの役割と基本的な使い方

TypeScriptでの「extends」は、主にクラスの継承に使用されるキーワードです。

このキーワードを用いることで、既存のクラスからプロパティやメソッドを受け継ぎながら、新たなクラスを作成することができます。

例を見てみましょう。

// 親クラス
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    move() {
        console.log(this.name + "は移動します。");
    }
}

// 子クラス
class Dog extends Animal {
    bark() {
        console.log("ワンワン");
    }
}

// 子クラスのインスタンス生成
const myDog = new Dog("ポチ");
myDog.move(); // ポチは移動します。
myDog.bark(); // ワンワン

このコードでは、Animalクラスを基にしたDogクラスを作成しています。

そして、DogクラスはAnimalクラスからmoveメソッドを受け継ぎつつ、新たにbarkメソッドを持つようになっています。

このように、extendsを使うことで、既存のクラスの機能を再利用しつつ、新しい機能を追加したクラスを効率よく定義することができます。

このコードを実行すると、まずmoveメソッドにより、「ポチは移動します。」と表示され、次にbarkメソッドにより、「ワンワン」と表示されます。

●extendsの使い方

TypeScriptにおけるextendsキーワードは、クラスベースのオブジェクト指向プログラムにおける継承をサポートします。

継承とは、既存のクラスから新しいクラスを作成することで、新しいクラスは元のクラスの属性やメソッドを継承します。

これにより、コードの再利用性が向上し、新しいクラスに追加的な機能や属性を追加することが容易になります。

○サンプルコード1:基本的なクラスの継承

TypeScriptにおける基本的なクラス継承の方法を紹介します。

下記のコードは、動物を表す基本クラスAnimalと、Animalを継承して特定の動物、犬を表すDogクラスを定義しています。

// 基本クラス Animal の定義
class Animal {
    // Animal クラスのコンストラクタ
    constructor(public name: string) {}

    // speak メソッドの定義
    speak() {
        return this.name + "は音を出します。";
    }
}

// Animal クラスを継承した Dog クラスの定義
class Dog extends Animal {
    // Dog クラス独自のメソッド bark の定義
    bark() {
        return this.name + "はワンワンと鳴きます。";
    }
}

const myDog = new Dog("ポチ");
console.log(myDog.speak());  // ポチは音を出します。
console.log(myDog.bark());  // ポチはワンワンと鳴きます。

このコードでは、Animalクラスを定義しており、それに名前を持つ動物の一般的な動作を示すspeakメソッドがあります。

Dogクラスは、このAnimalクラスを継承し、犬特有の鳴き声を返すbarkメソッドを持っています。

このコードを実行すると、犬のオブジェクトmyDogAnimalから継承されたspeakメソッドと、Dogクラス独自のbarkメソッドの両方を使用することができます。

結果、次の出力が得られます。

ポチは音を出します。
ポチはワンワンと鳴きます。

○サンプルコード2:抽象クラスを継承して具体的な実装を追加

抽象クラスとは、具体的な実装がされていないメソッドやプロパティを持つことができる特別なクラスのことを指します。

この抽象クラスを継承する際、子クラスではこれらの抽象メソッドやプロパティに具体的な実装を提供する必要があります。

TypeScriptで抽象クラスを定義し、それを継承して具体的な実装を追加するサンプルコードを紹介します。

// 抽象クラスAnimalを定義
abstract class Animal {
    abstract name: string;  // 抽象プロパティ

    // 抽象メソッド
    abstract makeSound(): void;

    // 通常のメソッド
    move(): void {
        console.log("移動する");
    }
}

// 抽象クラスAnimalを継承したDogクラス
class Dog extends Animal {
    name = "ワンちゃん";

    // 抽象メソッドの実装
    makeSound(): void {
        console.log("ワンワン!");
    }
}

const dog = new Dog();
dog.move();
dog.makeSound();

このコードでは、抽象クラスAnimalを使って、動物が持っている共通の特徴や動作を定義しています。

具体的には、名前を表すnameという抽象プロパティや、動物が発する音を表すmakeSoundという抽象メソッドが定義されています。

次に、Dogクラスでは、Animalクラスを継承しており、nameプロパティやmakeSoundメソッドに具体的な実装を提供しています。

このコードを実行すると、Dogクラスのインスタンスdogを作成して、moveメソッドとmakeSoundメソッドを呼び出します。

その結果、コンソールには次の出力が表示されます。

移動する
ワンワン!

○サンプルコード3:複数のインターフェースを継承

TypeScriptでは、複数のインターフェースを1つのクラスやインターフェースに継承することができます。

これは、JavaやC#などの他のオブジェクト指向言語でも一般的に見られる特徴ですが、TypeScriptでもこの特徴を効果的に活用することができます。

実際のサンプルコードを見てみましょう。

// インターフェース1
interface Runnable {
    run(): void;
}

// インターフェース2
interface Flyable {
    fly(): void;
}

// インターフェースを複数継承したクラス
class SuperHero implements Runnable, Flyable {
    run(): void {
        console.log("走る!");
    }

    fly(): void {
        console.log("飛ぶ!");
    }
}

このコードでは、Runnableflyableという2つのインターフェースを定義しています。

そして、SuperHeroクラスでは、これらのインターフェースをimplementsキーワードを使用して複数継承しています。

これにより、SuperHeroクラスはrunメソッドとflyメソッドの両方を持つことができます。

このコードを実行すると、次のような結果になります。

スーパーヒーローのインスタンスを作成して、そのメソッドを実行するコードを追記します。

const hero = new SuperHero();
hero.run(); // 走る! と表示
hero.fly(); // 飛ぶ! と表示

○サンプルコード4:継承を使ったポリモーフィズム

TypeScriptの魅力的な機能の一つとして、JavaやC#などの他のオブジェクト指向プログラミング言語と同じように、ポリモーフィズムを実現することができます。

ここでは、TypeScriptを用いて、ポリモーフィズムを実現する方法について解説します。

まず、ポリモーフィズムとは何か、簡単に説明します。

ポリモーフィズムは、オブジェクト指向プログラミングの大事な概念の一つであり、異なるクラスのオブジェクトを、共通のインターフェースや親クラスを介して同一視して取り扱うことができる機能です。

これにより、異なるオブジェクトを一貫した方法で操作することができるのです。

TypeScriptでポリモーフィズムを実現するためのサンプルコードを紹介します。

// 親クラスとしてAnimalを定義
class Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    // Animalクラスにspeakメソッドを定義
    speak(): string {
        return "何かの音";
    }
}

// DogクラスはAnimalクラスを継承
class Dog extends Animal {

    // speakメソッドをオーバーライド
    speak(): string {
        return "ワンワン";
    }
}

// CatクラスもAnimalクラスを継承
class Cat extends Animal {

    // speakメソッドをオーバーライド
    speak(): string {
        return "ニャー";
    }
}

let dog: Animal = new Dog("ポチ");
let cat: Animal = new Cat("タマ");

console.log(dog.speak());  // ワンワン
console.log(cat.speak());  // ニャー

このコードでは、Animalクラスを親クラスとして、その子クラスとしてDogクラスとCatクラスを定義しています。

それぞれの子クラスでは、親クラスのspeakメソッドをオーバーライドしており、動物に応じた音を返すようにしています。

コードを実行すると、それぞれの動物の特有の音、つまり”ワンワン”や”ニャー”といった結果が得られます。

このように、異なるクラスのオブジェクトでも、共通のインターフェースを持つ親クラスを介して、同じメソッドを呼び出すことができるのが、ポリモーフィズムの魅力です。

●extendsの応用例

TypeScriptの「extends」キーワードを一歩先に進めて考えると、さまざまな驚きの応用例が考えられます。

基本的な使い方だけでなく、より高度な型定義や設計の手法にも活用することができます。

今回は、その中から特に実践的な一例をご紹介いたします。

○サンプルコード5:ジェネリックスとextendsを組み合わせた高度な型定義

TypeScriptのジェネリックスを使用すると、型をパラメータ化して再利用性を高めることができます。

そして、このジェネリックスと「extends」を組み合わせることで、特定の型に制約を持たせた高度な型定義を行うことができます。

例として、特定のオブジェクトにnameプロパティが存在することを保証する型を定義してみましょう。

// ジェネリックスを使って型Tがnameプロパティを持つことを保証する型を定義
type HasName<T extends { name: string }> = T;

// この型を使用すると、nameプロパティがないとコンパイルエラーになる
const person: HasName<{ name: string, age: number }> = {
  name: "Taro",
  age: 25
};

このコードでは、ジェネリックスTがnameプロパティを持つことを保証する型「HasName」を定義しています。

そして、personオブジェクトを定義する際にこの型を使用しているため、nameプロパティが必須となります。

このような方法で、ジェネリックスとextendsを組み合わせて型の安全性を向上させることができます。

このコードを実行すると、特にエラーは発生せず、personオブジェクトが正常に定義されます。

ただし、nameプロパティを省略すると、TypeScriptのコンパイラによりエラーが報告されることが予想されます。

このように、ジェネリックスとextendsを組み合わせることで、コードの品質や安全性を向上させることができます。

○サンプルコード6:Mixinを利用した複数のクラスの機能統合

TypeScriptでは、一つのクラスが複数の他のクラスの機能を取り込むための方法として、「Mixin」を利用することができます。

これは、JavaScriptやその他の多くの言語で見られるクラスベースの継承とは異なり、特定の機能だけを取り込むことができるため、より柔軟なコード構造を構築することができます。

ここで、Mixinの使い方を示す簡単なサンプルコードを紹介します。

// 飛べる動物の振る舞いを定義するMixin
class Flyable {
    fly() {
        console.log("飛びます");
    }
}

// 泳げる動物の振る舞いを定義するMixin
class Swimmable {
    swim() {
        console.log("泳ぎます");
    }
}

// 鳥クラスは飛べる動物の振る舞いを取り込む
class Bird extends Flyable {
}

// 鴨クラスは飛べる動物と泳げる動物の振る舞いを両方取り込む
class Duck extends Flyable {
    // Swimmableの機能も取り込む
    swim: () => void = Swimmable.prototype.swim;
}

const duck = new Duck();
duck.fly();  // 飛びます
duck.swim(); // 泳ぎます

このコードでは、Flyableという名前のMixinが飛べる動物の振る舞いを定義しています。

同様に、SwimmableというMixinは泳げる動物の振る舞いを定義しています。

そして、BirdクラスはFlyableを継承して飛ぶ機能を持つようにしています。

更に進めて、DuckクラスはFlyableを継承し、更にSwimmableの泳ぐ機能も取り込んでいます。

このサンプルコードを実行すると、新しく作成した鴨のインスタンスは飛びますが、それと同時に泳ぐ機能も持っているため、泳ぐ振る舞いもすることができます。

○サンプルコード7:条件付き型と組み合わせた型のカスタマイズ

TypeScriptには、型をより柔軟にカスタマイズする機能として「条件付き型」が提供されています。

条件付き型を使用することで、型の条件に基づいて異なる型を返すことが可能となります。

今回は、この条件付き型を「extends」キーワードと組み合わせたカスタマイズについて詳しく解説します。

まず、条件付き型の基本的な文法を確認しましょう。条件付き型は、T extends U ? X : Yの形式で書かれます。

ここで、TUを継承している場合は型Xを、そうでない場合は型Yを返します。

下記のサンプルコードでは、条件付き型を使って、TypeOrString<T>という型を定義しています。

この型は、Tstringを継承している場合はTを、そうでない場合はstringを返すというものです。

// 条件付き型の基本的な使用例
type TypeOrString<T> = T extends string ? T : string;

// 使用例
let a: TypeOrString<number>;  // string型と推論される
let b: TypeOrString<"hello">; // "hello"型と推論される

このコードでは、TypeOrString<number>string型として、TypeOrString<"hello">は文字列リテラル型の"hello"として推論されます。

理由は、numberstringを継承していないのでstring型を返し、"hello"stringを継承しているため"hello"型を返すからです。

さらに、この条件付き型を使って、もっと複雑な型のカスタマイズを行うこともできます。次のサンプルコードでは、DeepReadonly<T>という型を定義しています。

この型は、オブジェクトのすべてのプロパティを再帰的に読み取り専用にします。

// 再帰的にオブジェクトのすべてのプロパティを読み取り専用にする型
type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 使用例
type Obj = {
  a: {
    b: number;
    c: {
      d: string;
    };
  };
  e: string;
};

const obj: DeepReadonly<Obj> = {
  a: {
    b: 123,
    c: {
      d: "hello"
    }
  },
  e: "world"
};

// objの全てのプロパティが読み取り専用になっているため、変更操作はエラーとなる
// obj.a.b = 456;  // エラー!

上記のコードのDeepReadonly<T>は、オブジェクトの各プロパティを再帰的に読み取り専用にします。

DeepReadonly<Obj>を使用することで、Obj型のすべてのプロパティが読み取り専用になります。

そのため、コメントアウトされたobj.a.b = 456;のような変更操作を試みると、TypeScriptのコンパイラはエラーを報告します。

○サンプルコード8:ユーティリティ型と組み合わせた型の変換

TypeScriptは、JavaScriptに静的型を追加する強力な言語であり、その強力な型システムにより、高度な型操作が可能となっています。

ここでは、TypeScriptの「extends」キーワードと、TypeScriptが提供するユーティリティ型との組み合わせについて解説します。

ユーティリティ型は、TypeScriptで提供される既存の型を簡単に変更・変換するための型です。

例えば、Partial<T>, Readonly<T>, Pick<T, K>などがユーティリティ型の一例です。

これらを「extends」と組み合わせることで、さらに柔軟な型操作が可能になります。

下記のサンプルコードでは、ユーティリティ型と「extends」を使用して、特定の型から特定のプロパティを取り出す方法を表しています。

interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type Filter<T, U> = {
    [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];

type StringKeys = Filter<Todo, string>;

このコードでは、Todoというインターフェースを定義しています。

次に、Filterというジェネリックス型を定義しています。

この型は、指定された型Tから、指定された型Uに一致するプロパティのキーだけを取り出す役割を持っています。

最後に、このFilter型を使用して、Todoインターフェースの中でstring型のプロパティだけのキーを取得しています。

このコードを実行すると、StringKeys型はtitle | descriptionという型として推論されます。

これは、Todoインターフェースの中でstring型のプロパティであるtitledescriptionのキーだけが取り出されるためです。

○サンプルコード9:アクセス修飾子との組み合わせ

TypeScriptの「extends」を理解する上で、アクセス修飾子との組み合わせも非常に重要です。

アクセス修飾子は、クラスのプロパティやメソッドがどのようにアクセスされるかを制限する機能です。

具体的には「public」、「private」、「protected」などがあります。

では、アクセス修飾子と「extends」を組み合わせたとき、どのような挙動になるのでしょうか。

それを明らかにするためのサンプルコードを紹介します。

class BaseClass {
    public publicProp: string = "public";
    private privateProp: string = "private";
    protected protectedProp: string = "protected";
}

class DerivedClass extends BaseClass {
    showProperties() {
        console.log(this.publicProp); // 親クラスのpublicプロパティはアクセス可能
        // console.log(this.privateProp); // エラー: 'privateProp'はクラス 'BaseClass'のみでアクセス可能です。
        console.log(this.protectedProp); // 親クラスのprotectedプロパティもアクセス可能
    }
}

const obj = new DerivedClass();
obj.showProperties();

このコードでは、BaseClassという親クラスがあり、その中に「public」、「private」、「protected」の3つのアクセス修飾子を持つプロパティが定義されています。

そして、DerivedClassという子クラスがこの親クラスを継承しています。

showPropertiesというメソッド内で、継承したプロパティへのアクセスを試みています。

その結果、publicPropprotectedPropはアクセス可能であり、値を正しく出力することができます。

しかし、privatePropprivate修飾子によって、継承先のクラスからはアクセスできません。

このコードを実行すると、親クラスのpublicプロパティはアクセス可能であるため、「public」という文字列が表示されます。

一方、privatePropにはアクセスできないため、その部分はコメントアウトしています。

最後に、protectedPropに関しても正常にアクセスでき、「protected」という文字列が表示されます。

○サンプルコード10:オーバーライドを使ったメソッドの拡張

TypeScriptにおいて、クラス継承時に親クラスのメソッドを子クラスで上書きすることをオーバーライドと言います。

オーバーライドは、既存のメソッドの振る舞いを変更したり、新しい処理を追加する際に非常に役立ちます。

具体的な例として、動物クラスを持ち、それを継承する犬クラスを考えます。

動物クラスには「鳴く」というメソッドが定義されているとし、犬クラスではこの「鳴く」メソッドをオーバーライドして「ワンワン」と鳴くように拡張します。

// 親クラス:動物クラス
class Animal {
    // 鳴くメソッド
    sound(): string {
        return '何かの音';
    }
}

// 子クラス:犬クラス
class Dog extends Animal {
    // 親クラスのsoundメソッドをオーバーライド
    sound(): string {
        return 'ワンワン';
    }
}

const dog = new Dog();
console.log(dog.sound());  // ワンワン

このコードでは、Animalクラスに定義されているsoundメソッドを、Dogクラスでオーバーライドしています。

そのため、Dogクラスのインスタンスでsoundメソッドを呼び出すと、「ワンワン」という結果が返されます。

このコードを実行すると、”ワンワン”という文字列がコンソールに表示されます。

これはDogクラスにおいてオーバーライドしたsoundメソッドが呼ばれるためです。

●注意点と対処法

TypeScriptでextendsを用いる際、そのパワフルさを活かしながら、いくつかのトラブルや不具合を回避するための知識が求められます。

ここでは、extendsを利用する際の主な注意点と、それに対する対処法を詳しく解説します。

○継承の際のコンストラクタの注意点

TypeScriptでのクラスの継承時、特にconstructorに関しては注意が必要です。

親クラスがconstructorを持つ場合、子クラスもコンストラクタを持つ必要があります。

また、子クラスのconstructor内でsuper()を呼び出すことは必須です。

class Parent {
    constructor(name: string) {
        console.log(name);
    }
}

class Child extends Parent {
    constructor(name: string, age: number) {
        super(name);  // 親クラスのconstructorを呼び出す
        console.log(age);
    }
}

このコードでは、ChildクラスはParentクラスを継承しています。

Childクラスのconstructor内でsuper(name)を呼び出すことで、親クラスのconstructorが実行されます。

このコードを実行すると、Childクラスのインスタンスを作成した際に、名前と年齢が順にコンソールに表示されます。

○抽象クラスとの違い

TypeScriptには、実装が存在せず、派生クラスでの実装が必須とされるabstract classというものも存在します。

extendsを用いて継承する際、この抽象クラスと通常のクラスの違いを理解しておくことは非常に重要です。

abstract class AbstractClass {
    abstract greet(): void;
}

class ConcreteClass extends AbstractClass {
    greet() {
        console.log('Hello!');
    }
}

このコードでは、AbstractClassgreetという抽象メソッドを持つ抽象クラスです。

これを継承するConcreteClassは、この抽象メソッドgreetを具体的に実装しています。

このコードを実行すると、ConcreteClassのインスタンスを作成し、greetメソッドを呼び出すと、”Hello!”とコンソールに表示されます。

○循環参照の問題と解決策

TypeScriptでは、2つのクラスが互いに継承し合う、いわゆる循環参照が発生すると、コンパイルエラーが生じます。

これは、クラスの定義が完了する前に他のクラスがそのクラスを参照しようとすると、未定義の状態で参照されるためです。

この問題を解決するためには、継承関係を見直し、循環参照を回避する設計にすることが求められます。

また、モジュールの分割やインターフェースの使用を検討することで、問題を回避することも可能です。

例えば、次のようにインターフェースを利用して、循環参照を回避することができます。

interface IGreet {
    greet(): void;
}

class ClassA implements IGreet {
    greet() {
        console.log('ClassA says Hello!');
    }
}

class ClassB implements IGreet {
    greet() {
        console.log('ClassB says Hello!');
    }
}

このコードでは、IGreetインターフェースを実装することで、ClassAClassBはそれぞれ独立してgreetメソッドを持つことができます。

循環参照のリスクを避けつつ、同じメソッドの実装を持つクラスを作成することができます。

●カスタマイズ方法

TypeScriptでは、コードの再利用性と拡張性を高めるための多くのテクニックがあります。

その中でも、extendsを使用して、既存のクラスやインターフェースを拡張する際のカスタマイズ方法について解説します。

○メソッドチェイニングを利用したフレキシブルな継承

メソッドチェイニングは、一つのメソッドの呼び出し結果に別のメソッドを連鎖的に呼び出すことを指します。

これを使うことで、TypeScriptの継承においても、より簡潔に、かつ、直感的にコードを書くことができます。

class BaseClass {
    value: string;
    constructor(value: string) {
        this.value = value;
    }
    setVal(newVal: string): this {
        this.value = newVal;
        return this;
    }
    showVal(): this {
        console.log(this.value);
        return this;
    }
}

class ExtendedClass extends BaseClass {
    additionalValue: string;
    setAdditionalVal(val: string): this {
        this.additionalValue = val;
        return this;
    }
    showAdditionalVal(): this {
        console.log(this.additionalValue);
        return this;
    }
}

const obj = new ExtendedClass('Initial');
obj.setVal('Updated').showVal().setAdditionalVal('NewValue').showAdditionalVal();

このコードでは、BaseClasssetValshowValのメソッドを定義しています。

これらのメソッドは、自身のインスタンスを返すことで、メソッドチェイニングを可能にしています。

ExtendedClassは、BaseClassを継承しており、さらに独自のメソッドsetAdditionalValshowAdditionalValを持っています。

これらも同様にメソッドチェイニングが可能です。

上記のコードを実行すると、次のような出力が得られます。

Updated
NewValue

このように、メソッドチェイニングを利用すれば、継承されたクラスのインスタンスメソッドを連鎖的に呼び出すことができ、コードの可読性や効率が向上します。

○Decoratorを用いた機能の追加

Decoratorは、TypeScriptでクラスやメソッド、プロパティに特定の動作や機能を追加するためのものです。

これを利用することで、継承したクラスに対して、特定の機能や動作を追加することが容易になります。

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function(...args: any[]) {
        console.log(`Called ${propertyKey} with arguments: ${args}`);
        return originalMethod.apply(this, args);
    }
}

class MyClass {
    @log
    multiply(a: number, b: number): number {
        return a * b;
    }
}

const instance = new MyClass();
instance.multiply(5, 3);

このコードでは、logというDecoratorを定義しています。

このDecoratorは、指定されたメソッドが呼び出される際に、メソッド名と引数をログとして出力します。

MyClassmultiplyメソッドには、@logのDecoratorが適用されています。

上記のコードを実行すると、次のような出力が得られます。

Called multiply with arguments: 5,3

まとめ

TypeScriptは、JavaScriptのスーパーセットとして、型安全性を提供する強力なツールです。

中でも、extendsキーワードは、クラスやインターフェースの継承を容易に行うことができる機能として、多くのプログラマーに支持されています。

この記事では、extendsの基本的な使い方から、応用例、注意点、そしてカスタマイズ方法まで、詳細に渡って解説を行いました。

extendsを使用することで、TypeScriptの持つ強力な型システムを活かし、より堅牢なプログラムを実現することができるのです。

この記事を参考に、TypeScriptのextendsを活用して、効果的なプログラミングを楽しんでください。