[TypeScript]ユーティリティ型で作成した型をもとに、元のジェネリクス型を取得する方法

ユーティリティ型を用いて作成した型をもとに、元の型(ジェネリクス型として指定された型)を取得する方法についてメモ。
結論からいうと、inferを使えばできます。
参考:infer | TypeScript入門『サバイバルTypeScript』

type TYPE_OBJ = {
    str: string,
    num?: number
}

/*
  {
    str: string;
    num: number;
  }
 */
type REQUIRED_OBJ = Required<TYPE_OBJ>

// Required<T>で作成された型なら、Tの型を取得する
type ORG_REQUIRED<T> = T extends Required<infer U> ? U : never

/*
  {
    str: string;
    num?: number;
  }
 */
type ORG_TYPE = ORG_REQUIRED<REQUIRED_OBJ>

正直、これができるのは最初、不思議でした。
だって、上記だとREQUIRED_OBJの型は{str: string;num: number;}ですが、この場合、Required<{str?: string;num?: number;}>の可能性も、Required<{str?: string;num: number;}>の可能性もあるわけです。
だけど、ちゃんと元の型({str: string;num?: number;})を取得できています。どういう指定で作成されたかということを内部でもっているということなんでしょうね。

Recordみたいに、ジェネリクス引数を2つ以上とるユーティリティ関数の場合、取得しないほうをanyとすればうまくいきます。

// 「{[x: string]: number;}型になる
type StringNumber = Record<string, number>;

// string型になる
type FirstStringNumber = StringNumber extends Record<infer T, any> ? T : unknown

// number型になる
type SecondStringNumber = StringNumber extends Record<any, infer T> ? T : unknown

ただ、すべてがうまくいくわけではないようで、ReturnTypeだとうまくいきませんでした。

const numToStr = (num:number): string => {
    return num.toString()
}

// string型になる
type RETURN_NUMTOSTR = ReturnType<typeof numToStr>

// 「(...args: any) => any」型になってしまう
type ORG_NUMTOSTR = RETURN_NUMTOSTR extends ReturnType<infer T> ? T : any

詳しくは調べてませんが、ReturnTypeは内部でinferを利用しているようです。もしかしたら、inferを使ったユーティリティ型で作成した型のジェネリクス型はうまく取得できないのかもしれないです。
なので、最終的には想定通りの型になっているかは確認したほうがよさそうですね。

コメント

タイトルとURLをコピーしました