🦅

TypeScriptのextendsを使いこなす:型引数の制約とクラスの継承

2024/06/18に公開

TypeScriptのextendsは、2つの異なる場面で使用されます。同じキーワードですが、用途が異なるため紛らわしいと感じるかもしれません。今回は、その使い方についてまとめます。

extendsは以下の2つの場面で使われます。

  1. 型引数
  2. クラスの継承

型引数に制約を設定するパターン

このパターンでは、型引数に制約を設定する際にextendsを使用します。

まず、型引数とは何かを説明します。型を定義する際に、型を引数として渡して使うことができます。動物を例にとって、以下のように型定義を行います。

// 鳥には足と羽がある
type Bird = {
    leg:number;
    blade:number;
}
// 犬には足がある
type Dog = {
    leg:number;
}
// 動物には名前と体がある
type Animal<T> = {
    name:string;
    body: T
}

const hawk: Animal<Bird> = {
    name: 'タカ',
    body: {
        leg:2,
        blade: 2
    }
}

const shiba: Animal<Dog> = {
    name: 'しば犬',
    body: {
        leg:4,
    }
}

上記の例だと、型引数Tは何でも許容される状態です。これでは不都合が生じる可能性があるため、TypeScriptには型引数に制約を設定する機能が備わっています。ここでextendsが登場します。

空を飛べる動物に限定した型SkyAnimalを定義してみます。

type Bird = {
    leg:number;
    blade:number;
}

type Dog = {
    leg:number;
}

// 羽を持っている
type HasBlade = {
    blade: number;
}
// 空を飛べる動物に限定
type SkyAnimal<T extends HasBlade> = {
    name:string;
    body: T
}

const hawk: SkyAnimal<Bird> = { // これはOK
    name: 'タカ',
    body: {
        leg:2,
        blade: 2
    }
}

const shiba: SkyAnimal<Dog> = { // これはNG
    name: 'しば犬',
    body: {
        leg:4,
    }
}

NGの場合はこのようなTSエラーが発生し、型定義とずれていることを教えてくれます。

Type 'Dog' does not satisfy the constraint 'HasBlade'.
  Property 'blade' is missing in type 'Dog' but required in type 'HasBlade'.(2344)

このように、型引数に制約を設定する際に使われるのが、extendsの1つ目の使い方です。

クラスの継承で使われるパターン

他の言語でもよくある使われ方で、クラス定義の際に親クラスを継承できます。

クラス名 extends 親クラス

Catという親クラスを継承したStrayCatとCheetahというクラスを定義してみます。

class Cat {
    name:string;
    runningSpeed:number = 0;

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

    public getMessage() {
        return `${this.name}${this.runningSpeed}km/hで走る`;
    }
}

class StrayCat extends Cat {
    runningSpeed = 40;
}

class Cheetah extends Cat {
    runningSpeed = 100;
}

const strayCat = new StrayCat('ノラネコ');
console.log(strayCat.getMessage());  // ノラネコは40km/hで走る

const cheetah = new Cheetah('チーター');
console.log(cheetah.getMessage());  // チーターは100km/hで走る

共通するプロパティやメソッドを、子クラスの方では書かなくて良いので実装やメンテナンスが楽になりますね。これがextendsの2つ目の使い方です。

参考

https://gihyo.jp/book/2022/978-4-297-12747-3

Discussion