TypeScriptの`infer`を一撃で理解する
準備運動
たとえば「Foo
型要素を持つ配列」や「Foo
型でfulfillされるPromise
」、「Foo
型のプロパティ"abc"を持つオブジェクト」の型を定義しようとすると下記のようになる。
type FooArray = Foo[]; type FooPromise = Promise<Foo>; type FooContainer = { abc: Foo };
この記事にたどり着いた人であればここまでは理解できるはず。
infer
の使いどころ
では逆に、「何かの型を持つ配列」や「何かの型でfulfillされるPromise
」、「何かの型のプロパティ"abc"を持つオブジェクト」の「何かの型」を得たい場合はどうするか。
ここでinfer
の出番となる。
// 注意: このコードは型安全ではありません。 type ArrayOf<T> = T extends (infer U)[] ? U : any; type PromiseOf<T> = T extends Promise<infer U> ? U : never; type AbcType<T> = T extends { abc: infer U } ? U : unknown;
ここのinfer U
という表現で取り出したい型にU
という名前を付け、後から参照している。
T extends ... ? ... : ...
は条件演算子のように型を選択するのに使用するが、infer
はこのextends
の条件部分でなければ使用することができない。
この例の場合、false側のほうの型は使わないのでany
でもnever
でもなんでもいい。
型安全なinfer
T
の型には静的型チェックの制限をかけるのが通例である。
前述の例だとT
の型次第ではfalseの条件に合致してしまうため、意図せずfalse側のほうの型になってしまう可能性がある。
type ArrayOf<T extends any[]> = T extends (infer U)[] ? U : any; type PromiseOf<T extends Promise<any>> = T extends Promise<infer U> ? U : never; type AbcType<T extends { abc: any }> = T extends { abc: infer U } ? U : unknown;
実用例
TypeScriptにはいくつか事前定義された型があり、その中に「関数の引数の型をタプルとして取得する型」Parameters<Type>や「関数の戻り値の型を取得する型」ReturnType<Type>がある。
これらはinfer
を用いることで実現しているが、実際のコードはこの記事の内容を理解しただけで書けるような単純なものになっている。
どんなコードかを予想してから下記リンク先から確かめてみると理解が深まるかもしれない。
TypeScript/lib.es5.d.ts at v4.0.2 · microsoft/TypeScript · GitHub
まとめ
infer
は、ある型の中に含まれる何かの型を取り出すのに用いる。T extends ... ? ... : ...
の条件部分にしか使用できない。- 取り出したい型のところに
infer U
を当てはめると、取り出した型にU
と名前を付けてtrue側の部分で用いることができる。