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 の呼び出しを移しましょう。