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側の部分で用いることができる。