サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

TypeScript: 型推論


TypeScript公式Handbook「Type Inference」をもとにした解説です。TypeScriptは、型づけされていないデータの型を推論します。その推論が、何にもとづいてどのようになされるのかについてご説明しましょう。

01 型の基本的な決め方

あらかじめ型が与えられていないとき、推論で定まる場合がいくつかあります。たとえば、代入された値から型は決まります。


let x = 1;  // number型と推論される

変数やメンバーの初期値や関数(メソッド)の引数のデフォルト値、あるいは戻り値についても推論は働きます。多くの場合、結果はわかりやすく、納得のいくものでしょう。

02 共通する最適な型

複数の式から型を推論しなければならないときは、共通するもっとも適した型が選ばれます。たとえば、つぎの配列の型は、number[]と推論されます。値nullnumber型にも合うからです[*1]


let x = [0, 1, null];  // number[]型と推論される


つぎの配列で、numberbooleanでは型が合いません。したがって、型は(number | boolean)[]と推論されます。


let x = [0, true, null];  // (number | boolean)[]型と推論される

つぎのように基本クラス(Point)から、ふたつのサブクラス(VectorPolar)を定めたとします。


class Point {
	constructor(public x = 0, public y = 0) {}
}
class Vector extends Point {
	constructor(x = 0, y = 0) {
		super(x, y);
	}
	getLength() {
		let squaredSum = this.x * this.x + this.y * this.y;
		return Math.sqrt(squaredSum);
	}
}
class Polar extends Point {
	radius: number;
	angle: number;
	constructor(radius = 0, angle = 0) {
		let x = radius * Math.cos(angle);
		let y = radius * Math.sin(angle);
		super(x, y);
        this.radius = radius;
        this.angle = angle;
    }
}

3つのクラスのオブジェクトから共通する最適の型を決めれば、基本クラス(Point)になります。


let points = [new Point(), new Vector(), new Polar()];  // Point[]型と推論される

ただし、型は対象となるオブジェクトの中から選ばれます。基本クラスが含まれていなければ、継承まで含めた推論はされません。


let points = [new Vector(), new Polar()];  // (Vector | Polar)[]型と推論される
// points.push(new Point());  // 型が合わないのでエラー

スーパークラスで型づけするには、推論によることなく、型を与えなければならないのです。


let points: Point[] = [new Vector(), new Polar()]; 

[*1] tsconfig.jsonのcompilerOptionsstrictNullChecksオプションをtrueに定めると、nullがより厳しく確かめられます。nullnumber型に合いません。なお、オプションstricttrueを与えだ場合も、strictNullCheckstrueになります。

tsconfig.json

{
	"compilerOptions": {

		"strictNullChecks": true
	}
}

この場合、つぎの配列は型が(number | null)[]と推論されることになります。


let x = [0, 1, null];  // (number | null)[]型と推論される

03 文脈による型づけ

TypeScriptは、式の定め(文脈)から型が明らかになるとき、暗黙の型づけを行います。つぎのようにaddEventListener()メソッドmousedownイベントにリスナー関数が定められた場合、引数にはMouseEventオブジェクトが渡されると推論されます。すると、オブジェクトにないプロパティを参照すればエラーを示すのです(keyCodeKeyboardEventオブジェクトのプロパティです)。文脈から型が決まらないときは、anyとみなされます。


window.addEventListener('mousedown', function(eventObject) {
	console.log(eventObject.clientX);  // OK
	// console.log(eventObject.keyCode);  // プロパティがないのでエラー
});

型を明示して与えると、文脈による型に優先して用いられます。文脈による型を確かめてのエラーは出しません。


window.addEventListener('mousedown', function(eventObject: any) {
	console.log(eventObject.keyCode);  // undefined
});

文脈による型づけがよりどころとするのは、つぎのようなデータです。関数を呼び出す引数、代入式の右辺、型注釈、オブジェクトのメンバーや配列リテラルの要素、戻り値の式などにもとづいて推論されます。文脈による型づけは、前述した共通の最適な型と似た候補の選び方もします。つぎの関数(createPoints())は、サブクラス(VectorとPolar)のインスタンスを配列に納めて返します。スーパークラス(Point)のオブジェクトは含まれません。けれど、戻り値の型として与えました。そのため、最適な型としてスーパークラスが選ばれるのです。


function createPoints(): Point[] {
	return [new Vector(), new Polar()];
}
let points = createPoints();
points.push(new Point());  // エラーなし


作成者: 野中文雄
更新日: 2020年06月01日 最新の公式ドキュメントにもとづいて加筆・補正。
作成日: 2017年04月01日


Copyright © 2001-2017 Fumio Nonaka.  All rights reserved.