TypeScript 速習チュートリアル

TypeScript のクラス

TypeScriptは、JavaScriptのクラスに対して、型(Types)とアクセス修飾子(Visibility Modifiers)を追加します。

1. クラスメンバーの型付け (Members: Types)

クラスのメンバー(プロパティやメソッド)は、変数と同様に型アノテーションを使用して型定義を行います。

class Person {
  name: string;
}

const person = new Person();
person.name = "ジェーン";

2. アクセス修飾子(可視性) (Members: Visibility)

クラスのメンバーには、外部からのアクセスを制御するための特別な修飾子を付与できます。
TypeScriptには、主に3つのアクセス修飾子(Visibility Modifiers)が存在します。

  • public - (デフォルト)どこからでもクラスメンバーにアクセス可能
  • private - クラス内部からのみアクセス可能
  • protected - そのクラス自身、およびそれを継承したクラスからアクセス可能(詳細は後述の継承セクションで解説)

class Person {
  private name: string;

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

  public getName(): string {
    return this.name;
  }
}

const person = new Person("ジェーン");
console.log(person.getName()); // person.name は private なので、クラスの外部からはアクセスできません

クラス内での this キーワードは、通常そのクラスのインスタンスを指します。

3. パラメータプロパティ (Parameter Properties)

TypeScriptには、コンストラクタの引数にアクセス修飾子を追加することで、クラスメンバーを簡潔に定義できる便利な構文があります。

class Person {
  // name は private なメンバー変数として自動的に定義・初期化されます
  public constructor(private name: string) {}

  public getName(): string {
    return this.name;
  }
}

const person = new Person("ジェーン");
console.log(person.getName());

4. 読み取り専用メンバー (Readonly)

配列と同様に、readonly キーワードを使用することでクラスメンバーの変更を防ぐことができます。

class Person {
  private readonly name: string;

  public constructor(name: string) {
    // 宣言時、またはコンストラクタ内での初期化以降、name は変更できません
    this.name = name;
  }

  public getName(): string {
    return this.name;
  }
}

const person = new Person("ジェーン");
console.log(person.getName());

5. 継承:インターフェースの実装 (Inheritance: Implements)

インターフェースは、implements キーワードを使用して、クラスが従うべき型(構造)を定義するために使用されます。

interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }
}

クラスは、カンマ区切りで複数のインターフェースを実装することも可能です(例:class Rectangle implements Shape, Colored { ... })。

6. 継承:クラスの拡張 (Inheritance: Extends)

クラスは extends キーワードを使用して、別のクラスを継承(拡張)できます。
一つのクラスが継承できるのは、一つの親クラスのみです。

interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  public constructor(width: number) {
    super(width, width); // 親クラスのコンストラクタを呼び出す
  }

  // getArea は Rectangle から継承されます
}

7. オーバーライド (Override)

クラスが別のクラスを継承する場合、親クラスと同じ名前を持つメンバーを再定義(上書き)できます。
新しいバージョンのTypeScriptでは、override キーワードを使ってこれを明示的にマークできます。

interface Shape {
  getArea: () => number;
}

class Rectangle implements Shape {
  // protected を使用することで、Square のような継承先クラスからのアクセスを許可します
  public constructor(protected readonly width: number, protected readonly height: number) {}

  public getArea(): number {
    return this.width * this.height;
  }

  public toString(): string {
    return `Rectangle[width=${this.width}, height=${this.height}]`;
  }
}

class Square extends Rectangle {
  public constructor(width: number) {
    super(width, width);
  }

  // この toString は Rectangle の toString を上書きします
  public override toString(): string {
    return `Square[width=${this.width}]`;
  }
}

デフォルトではメソッドをオーバーライドする際の override キーワードは任意ですが、存在しないメソッドを誤ってオーバーライドしようとするのを防ぐのに役立ちます。設定の noImplicitOverride を使用すると、オーバーライド時にこのキーワードの使用を強制できます。

8. 抽象クラス (Abstract Classes)

他のクラスのベースクラス(親クラス)として使用することを前提とし、すべてのメンバーを実装しなくてもよいクラスを定義できます。
これには abstract キーワードを使用します。

未実装のまま残すメンバーにも abstract キーワードを付与します。

abstract class Polygon {
  public abstract getArea(): number;

  public toString(): string {
    return `Polygon[area=${this.getArea()}]`;
  }
}

class Rectangle extends Polygon {
  public constructor(protected readonly width: number, protected readonly height: number) {
    super();
  }

  public getArea(): number {
    return this.width * this.height;
  }
}