サイトトップ

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)から、ふたつのサブクラス(VectorとPolar)を定めます。


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()]; 

共通の最適な型がみつからない場合は、空のオブジェクト{}で型づけされます。メンバーがありませんので、プロパティを参照しようとすればエラーになります。けれど、型にかかわらないかぎりオブジェクトを使うことはでき、anyで型づけされるのとは異なり、つねに確認が行われます。

[*1] tsconfig.jsonのcompilerOptionsstrictNullCheckstrueに定めると、nullがより厳しく確かめられます。nullnumber型に合いません。

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.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());  // エラーなし


作成者: 野中文雄
作成日: 2017年4月1日


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