Menu

TypeScriptのコンストラクター: 引数プロパティとオーバーロード

TypeScriptでクラスのコンストラクターに型を付ける方法。型付きの引数と省略可能な引数、constructor(private name: string) のような引数プロパティ、フィールドの初期化の順序、コンストラクターのオーバーロード、サブクラスでの super の呼び出し、private コンストラクター、new を使ったコンストラクターの型を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

TypeScriptのコンストラクターは、引数に型が付いたクラスの constructor メソッドです。戻り値の型注釈はありません。常にそのクラスのインスタンスを作ります。

constructor(): Product と書くとエラー TS1093(Type annotation cannot appear on a constructor declaration)になります。new の動きについては、それ以外はすべて普通のJavaScriptです。

引数プロパティ

上のパターン(フィールドを宣言し、引数を受け取り、それをコピーする)はとてもよく使うので、TypeScriptには省略記法があります。コンストラクターの引数の前に public、private、protected、readonly を付けると、その引数がフィールドになります:

これはコードを生成する数少ないTypeScriptの機能のひとつです。コンパイラーが代入を書いてくれます:

class Product {
    name;
    price;
    sku;
    constructor(name, price, sku) {
        this.name = name;
        this.price = price;
        this.sku = sku;
    }
    // ...
}

型を消去するだけではないので、引数プロパティは Node の組み込みの型除去では動かず(node file.ts は ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX で失敗します)、コンパイラーの erasableSyntaxOnly オプションはエラー TS1294 として報告します。.ts ファイルを直接実行するプロジェクトでは、フィールドを手で書きます。

省略可能な引数とデフォルト引数

コンストラクターの引数も関数の引数と同じルールに従います。? で省略可能になり、デフォルト値を付けると省略可能になって値が補われ、必須の引数が先に来ます。

引数プロパティにもデフォルト値を付けられます。クラスの中での color の型は string | undefined なので、使うコードは先に undefined をチェックする必要があります。

オプションの多いコンストラクターなら、長い位置引数のリストより1つのオプションオブジェクトのほうが読みやすくなります: constructor(opts: { prefix: string; level?: "info" | "debug" })。

フィールドの初期化の順序

フィールドの初期化子はコンストラクターの本体より前に実行され、引数プロパティはコンストラクターの本体の最初で代入されます。そのため、フィールドの初期化子は引数プロパティを読めません。TypeScriptはこれを捕まえます:

index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.

実行時には初期化子が undefined を見て NaN を作ってしまいます。代わりにコンストラクターの本体で値を計算しましょう:

class Grid {
    cells: number;

    constructor(public size: number) {
        this.cells = size * size;
    }
}

console.log(new Grid(3).cells); // 9

コンストラクターのオーバーロード

クラスのコンストラクターの実装はちょうど1つですが、その上にオーバーロードシグネチャを複数並べられます。呼び出し側に見えるのはオーバーロードだけで、実装シグネチャはそのすべてと互換でなければなりません。

形が2つか3つを超えると、オーバーロードは読みにくくなります。わかりやすい名前の static ファクトリーメソッド(Color.fromHex("#ff8800")、Color.fromRgb(10, 20, 30))のほうがわかりやすいことが多く、1つの本体の中で型を絞り込む必要もありません。

サブクラスで super を呼ぶ

自分のコンストラクターを宣言する派生クラスは、親の引数で super(...) を呼ばなければならず、しかも this に触れる前に呼ぶ必要があります。呼び出しを忘れるとエラー TS2377(Constructors for derived classes must contain a 'super' call)、先に this を使うと TS17009('super' must be called before accessing 'this' in the constructor of a derived class)です。どちらもJavaScriptの実行時のルールをそのまま反映しています。

class Animal {
    constructor(public name: string) {}
}

class Dog extends Animal {
    tricks: string[];

    constructor(name: string, ...tricks: string[]) {
        super(name); // must come first
        this.tricks = tricks;
    }
}

const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }

コンストラクターを追加しないサブクラスは、同じ引数の型で親のコンストラクターを継承します。サブクラスについて詳しくは 継承 を参照してください。

private コンストラクターと非同期の初期化

private なコンストラクターはクラスの中からしか呼べません。外で new するとエラー TS2673 です。シングルトンや、非同期で行う必要がある初期化(コンストラクターは async にできません)のために、呼び出し側にファクトリーを使わせる方法です。

ここでの private はコンパイル時だけのルールです。出力されるJavaScriptには普通のコンストラクターがあるので、素のJavaScriptのコードからは new Connection(...) を呼べてしまいます。

コンストラクターの型

クラスを値として受け取るには、引数にコンストラクトシグネチャ new (...args) => Instance で型を付けます。typeof MyClass でも動きますが、引数がそのクラスのコンストラクターシグネチャと static メンバーに縛られます。

ConstructorParameters と InstanceType は、コンストラクターの型から引数のタプルとインスタンスの型を取り出します。ほかの関数用のヘルパーと一緒に ReturnType と Parameters で説明しています。

よくある質問

TypeScriptの引数プロパティとは何ですか?

修飾子(public、private、protected、readonly)付きのコンストラクターの引数は、フィールドの宣言と代入を一度に行います。constructor(private name: string) {} は、private name: string を宣言して this.name = name と書くことの省略形です。コンパイラーが出力にその代入を生成します。

TypeScriptのクラスは複数のコンストラクターを持てますか?

実装を複数持つことはできません。クラスのコンストラクターは1つですが、その上にオーバーロードシグネチャを複数書けば、呼び出し側からは別々の引数リストが見えます。Color.fromHex() や Color.fromRgb() のような static のファクトリーメソッドのほうが、オーバーロードよりわかりやすいこともよくあります。

TypeScriptでコンストラクターを async にできますか?

できません。コンストラクターは常に新しいインスタンスを返し、Promise は返しません。async constructor() はエラー TS1089('async' modifier cannot appear on a constructor declaration)で拒否されます。private コンストラクターと、非同期の処理をしてから new を呼ぶ static な async create() メソッドを使いましょう。

クラスのコンストラクターを引数として型付けするには?

コンストラクトシグネチャを使います: new (name: string) => User、特定のクラスなら typeof User です。ジェネリックなファクトリーは function make<T>(ctor: new () => T): T { return new ctor(); } のようになります。ConstructorParameters<typeof User> は引数リストをタプルとして返します。

「'super' must be called before accessing 'this'」と言われるのはなぜですか?

エラー TS17009 です。別のクラスを extends するクラスでは親のコンストラクターがオブジェクトを作るので、super(...) が実行されるまで this は存在しません。this に触れる行より上に super の呼び出しを移しましょう。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める