TypeScriptの型推論について学んだ

型推論とは

最近TypeScriptを使って開発していたけれど、大きな勘違いをしていた。
強い型付けをする言語ということなので、変数を定義するときに毎回型を宣言しなければいけないと思っていたのだ。
例えばこんな感じ。

let x: number = 3;

人間から見れば、この number はノイズでしかない。3は数値だと、誰が見てもわかるはずだ。
入力が手間なうえ、可読性を下げてしまう。

この場合は以下のような記述で問題ない。

let x = 3;

TypeScriptは3が数値であることを知っており、xを数値型とみなしてくれる。これを型推論という。

「最も共通する型」による型推論

この機会にもう少し公式ドキュメントを読んで深掘ってみる。
例えば以下の場合は、Arrayであることは間違いないが、その要素の型は何と推論されるだろうか?

let x = [0, 1, null];

TypeScriptは、提供される候補の型から「最も共通する型」を選ぶ。これは、他の全ての候補と互換性がある、すなわち他の候補がその型からの派生した型であるという意味だ。
上の場合、Arrayの中にはnumberとnullの2つの型があるが、TypeScriptでは、nullは全ての型の部分型(subtype)とみなされる。
このため、上の変数xの型はnumber[]と推論される。

ちなみに、この方法で型が選べない場合は、推論の結果が空のオブジェクト{}になってしまうので、最初に示したように自分で型を書いてやる必要がある。

「文脈上の型」による型推論

さきほどの共通する型による型推論は、平たく言うと右辺をもとに左辺の型を判定するという流れだった。
しかし、その逆で左辺から右辺の型を判定することもある。

window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};

上の場合、mouseEventの型は定義されていないはずだが、なぜエラーが起きるのだろうか?
TypeScriptは以下のように推論するようだ。

window.onmousedown は、mouseEvent型の引数を取る関数であることを知っている
・このため、右辺の引数 mouseEvent は、 mouseEvent 型であると推論される
・しかし、 mouseEvent 型に button というプロパティはない。そのためエラーとなる
developer.mozilla.org

このとき、function(mouseEvent: any) {...} のように、引数mouseEventの型を明示していれば、推論が上書きされてエラーは生じない。

人間が快適にコードを書けるよう、影でいろいろな苦労があるんだなあとしみじみ実感した。