🗂

【TypeScript】inferを理解する

2022/12/05に公開

背景

typeChallengesのAwaited型の問題に関する解答

type MyAwaited<T> =
  T extends Promise<infer Val> ? MyAwaited<Val>
  : T extends { then: (onfulfilled: (arg: infer Args) => any) => any } ? Args : T

ここで登場する infer が当初分からなかったため、調べたことを記事にメモします。

結論

https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types

  • conditional types という、extends句と三項演算子を用いて型条件を付与することができる使い方があります。
  • inferとはその条件付き型のextentds句内で、推論される型変数を導入するための宣言です。

conditional types

type Expected<T> = T extends "none" | "undefined" ? never : T;

このように、右辺でもextendsを用いることができ
extends A ? B : Cのような型条件での三項演算子が利用可能です。

infer

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

たとえば上記のジェネリクス型があったとします。

type ToString = (num: number) => string  //ToString型
// (...args: any[]) => infer R を満たす

type returnTypeToString = ReturnType<ToString>

ここでextends句を満たすToString型という関数の型がジェネリクスに入る場合を考えます。

(num: number) => string  

(...args: any[]) => infer R
//R = string 

infer R に該当する部分は string になるので、R = string となります。

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

つまり上記のケースだと、「R」は extends句を満たす関数の返り値ということになります。

Awaited

type MyAwaited<T> =
  T extends Promise<infer Val> ? MyAwaited<Val>
  : T extends { then: (onfulfilled: (arg: infer Args) => any) => any } ? Args : T

ここで一番最初のケースに戻ります。
ここでは型がPromiseであればPromiseの型引数をMyAwaitedの型引数にして再び実行する、
Promise<Promise<Promise<string>>>のような型だった場合の型引数を取り出すような型を表現していることになります。

Discussion