HTML5テクニカルノート
TypeScript: 型推論
- ID: FN1704001
- Technique: HTML5 / JavaScript
- Package: TypeScript 3.9
TypeScript公式Handbook「Type Inference」をもとにした解説です。TypeScriptは、型づけされていないデータの型を推論します。その推論が、何にもとづいてどのようになされるのかについてご説明しましょう。
01 型の基本的な決め方
あらかじめ型が与えられていないとき、推論で定まる場合がいくつかあります。たとえば、代入された値から型は決まります。
let x = 1; // number型と推論される
変数やメンバーの初期値や関数(メソッド)の引数のデフォルト値、あるいは戻り値についても推論は働きます。多くの場合、結果はわかりやすく、納得のいくものでしょう。
02 共通する最適な型
複数の式から型を推論しなければならないときは、共通するもっとも適した型が選ばれます。たとえば、つぎの配列の型は、number[]
と推論されます。値null
はnumber
型にも合うからです[*1]。
let x = [0, 1, null]; // number[]型と推論される
つぎの配列で、number
とboolean
では型が合いません。したがって、型は(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()];
[*1] tsconfig.jsonのcompilerOptions
でstrictNullChecks
オプションをtrue
に定めると、null
がより厳しく確かめられます。null
はnumber
型に合いません。なお、オプションstrict
にtrue
を与えだ場合も、strictNullChecks
がtrue
になります。
tsconfig.json{ "compilerOptions": { "strictNullChecks": true } }
この場合、つぎの配列は型が(number | null)[]
と推論されることになります。
let x = [0, 1, null]; // (number | null)[]型と推論される
03 文脈による型づけ
TypeScriptは、式の定め(文脈)から型が明らかになるとき、暗黙の型づけを行います。つぎのようにaddEventListener()
メソッドでmousedown
イベントにリスナー関数が定められた場合、引数にはMouseEvent
オブジェクトが渡されると推論されます。すると、オブジェクトにないプロパティを参照すればエラーを示すのです(keyCode
はKeyboardEvent
オブジェクトのプロパティです)。文脈から型が決まらないときは、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.