🚥

【TypeScript】条件型とinferキーワードを理解する

2023/05/15に公開

条件型とは

条件型は、与えられた型に応じて別の型を生成する機能です。構文は次のようなものです。
T extends U ? A : B
TUのサブタイプであればAを、そうでなければBを割り当てます。
三項演算子と同じことを型レベルで行っていると考えるとわかりやすいですね。

条件型利用例
type isString<T> = T extends string // Tはstringのサブタイプですか?
  ? true                            // そうであれば true
  : false                           // そうでなければ false

type A = isString<string> // true
type B = isString<number> // false

例のような型エイリアスだけでなく、インターフェース、クラス、パラメーター型、関数、メソッド内のジェネリックのデフォルト型の中で条件型を使うことができます。

分配条件型

条件型は数学の分配法則のような表現ができます。

(string | number) extends T ? A : B
// ↓これと等しい
(string extends T ? A : B) | (number extends T ? A : B)

inferキーワード

inferキーワードはT extends U ? A : BのUの中の型を参照できるようにするために使います。

配列の要素の型を取得するinferを使った条件型
(T[number]はTの要素の型を返します)

// inferを使わない条件型
type ElementType1<T> = T extends unknown[] // Tが配列だったら
  ? T[number]                              // Tの配列の要素の型を返す
  : T                                      // そうでなければTを返す
type A = ElementType1<number[]> // number
 
// 上と同じものをinferを使って書くと
type ElementType2<T> = T extends (infer U)[] // Tが配列だったら(要素の型をUと置く)
  ? U                                        // Uを返す
  : T                                        // そうでなければTを返す
type A = ElementType2<number[]> // number

組み込みの条件型

TypeScriptにはグローバルに利用可能な組み込みの条件型が用意されています。

Exclude<T,U>

Tに含まれるがUには含まれない型
TからUを除く)

type A = Exclude<number | string , string> // number
条件型によるExcludeの定義
type Exclude<T, U> = T extends U ? never : T;

Extract<T,U>

Tに含まれる型のうち、U割り当てることのできる型
TからUに含まれる型のみを抽出)

type A = Extract<number | string , string> // string
条件型によるExtractの定義
type Extract<T, U> = T extends U ? T : never;

NonNullable<T>

Tのnullとundefinedを除いた型

type A = {
  a: number | null
}
type B = NonNullable<A['a']> // number
条件型によるNonNullabeの定義
type NonNullable<T> = T extends null | undefined ? never : T

ReturnType<F>

関数の戻り値の型
(ジェネリックやオーバーロードされた関数はうまく動作しない場合があります。)

type A = ReturnType<(a:number) => string> // string
条件型によるReturnTypeの定義
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

InstanceType<C>

クラスコンストラクターのインスタンスの型

// 例1
type A = {new(): B}
type B = {b: number}
type I = InstanceType<A> // {b:number}

// 例2
class Person {
//...
}
type PersonType = InstanceType<typeof Person> // Person
条件型によるInstanceTypeの定義
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;

利用例がイメージしにくいですが、クラス定義にアクセスできない・クラス定義が複雑すぎる場合などに使うことができます。
https://github.com/Microsoft/TypeScript/issues/25998
https://zenn.dev/ytr0903/articles/905306671f39c8

Discussion