ymizushiの雑多なメモ

記憶に定着させるためあるいは思い出しやすくするためのメモ

TypeScriptのkeyofの謎仕様

TypeScriptには keyof というオペレータがある。

例えば、

type Point = { x: number; y: number };

という、型を定義した場合、

> type Point = { x: number; y: number };
undefined
> type P = keyof Point;
undefined
> var point: P = "x"
undefined
> var point: P = "y"
undefined
> var point: P = "z"
<repl>.ts:8:5 - error TS2322: Type '"z"' is not assignable to type 'keyof Point'.

8 var point: P = "z"
      ~~~~~

このように、keyをUnion typeに変換することが出来る。

それでは、以下のようなコードにおいてMapishはどのようにUnion Typeに変換されるだろうか。

type Mapish = { [k: string]: boolean };
type M = keyof Mapish;

普通に考えれば、type M = number になるはずだ。

しかし試しに打ち込んでみると、var mapish: M = "2" がコンパイルエラーにならず意図通りにはならない。

> var mapish: M = 1
undefined
> var mapish: M = 2
undefined
> var mapish: M = "2"
undefined // コンパイルエラーになって欲しい
> var mapish: M = []
<repl>.ts:14:5 - error TS2322: Type 'never[]' is not assignable to type 'string | number'. // string | number と認識されている

これは上記の最後の行で Type 'never[]' is not assignable to type 'string | number' と記述されていることから分かる通り、type M = string | number 型に変換されていることが原因だ。

これはなぜかというと、JavaScriptオブジェクトのkeyは常に文字列として扱われているからだ。 具体例でいうと、obj[0] は常にobj["0"] と等しいということ。

やはりTypeScriptを使うときは常にJavaScriptを意識しておいたほうが心構えとしてよさそうだ。

参考文献

www.typescriptlang.org