【TypeScript】Conditional Typeで型の条件分岐を行う
どうもフロントエンドエンジニアのoreoです。
今回は、型の条件分岐ができるConditional Typeについて整理します。
1 Conditional Typeについて
1-1 Conditional Type
Conditional Typeは、「条件型」とも呼ばれ、T extends U ? X : Y
の様な形をとります。三項演算子みたいなもので、T
がU
のサブタイプである場合はX
を返し、そうでない場合は、Y
を返します。
例えば、👇のIsNumber
の様に、Generics
型であるT
が、number
型のサブタイプであれば、boolean
リテラル型のtrue
を返し、それ以外の場合はfalse
を返します
type IsNumber<T> = T extends number ? true : false;
type T1 = IsNumber<10>; //type T1 = true
type T2 = IsNumber<"テスト">; //type T2 = false
type T3 = IsNumber<number>; //type T3 = true
<T>
のようようなGenerics
型に関しては、こちらの記事をご覧ください。
1-2 Union distribution(ユニオンの分配)
T extends U ? X : Y
において、T
が型変数かつユニオン型の場合、Union distributionという挙動が発生します。👇のように、T
がT1 | T2 | T3
などのユニオン型の場合、ユニオン型のそれぞれの要素に対して、Conditional Typeが適用されます。
(T1 | T2 | T3) extends U ? X : Y
//Union distributionにより、👇と同じになります。
(T1 extends U ? X : Y) | (T2 extends U ? X : Y) | (T3 extends U ? X : Y)
Union distributionは、ユーティリティ型のExtract
などで使用されています。Extract
は、👇のMyExtract
のように定義できます。
//ユーティリティ型のExtractを、MyExtractとして定義してみる
type MyExtract<T, U> = T extends U ? T : never;
MyExtract
を使用すると、ユニオン型のJobGrade
から、型SuperJobGrade
として、"S"
と"A"
を抽出することが可能です。
type JobGrade = "S" | "A" | "B";
type SuperJobGrade = MyExtract<JobGrade, "S" | "A">; //type SuperJobGrade = "S" | "A"
この場合、MyExtract
では、👇のように、Union distributionが発生しています。
("S" extends "S" | "A" ? "S" : never) | ("A" extends "S" | "A" ? "A" : never) | ("B" extends "S" | "A" ? "B" : never)
こちらのUnion distributionは、条件部分が型変数の場合のみ発生する点、注意ください(👇参考)。
1-3 infer
infer
は、Conditional TypeT extends U ? X : Y
のU
の部分で使うキーワードで、U
の型の条件とマッチした型を抽出し、それをX
、Y
で利用することができます。
infer
は、ユーティリティ型のReturnType
などで使用されています。ReturnType
は、👇のMyReturnType
のように定義できます。
//ユーティリティ型のReturnTypeを、MyReturnTypeとして定義してみる
type MyReturnType<T> = T extends (...args:any[])=> infer R ? R : never
MyReturnType
では、U
の部分に関数型(...args:any[])=> infer R
が定義されています。この場合、T
に渡された型が、関数型の場合、その関数型の返り値の型をinfer
がR
として抽出してくれます。
// () => number の返り値の型numberを抽出
type Num = MyReturnType<() => number>; //type Num = number
// (x: string) => string の返り値の型stringを抽出
type Str = MyReturnType<(x: string) => string>; //type Str = string
// (a: boolean, b: boolean) => boolean[] の返り値の型boolean[]を抽出
type Bools = MyReturnType<(a: boolean, b: boolean) => boolean[]>; //type Bools = boolean[]
2 最後に
Union distributionやinferに関しては、codeを読むだけでは、何が起きているのか全くわかりませんでしたが、一度その挙動を知ると便利ですね。自作のユーティリティ型を作る時に大活躍しそうです!
3 参考
Documentation - Conditional Types
TypeScript の条件型(Conditional Type)と infer キーワード - 30歳からのプログラミング
Discussion
Union distribution の説明ですが、以下で X1 が
boolean
ではなくfalse
になるので間違えていませんか…?これっぽいです
pandanoir様
ご指摘ありがとうございます。また、誤認しており大変申し訳ありませんでした、、!Union distribution部分を修正させていただきました!
このようなご指摘いただけるのは大変嬉しいです、ありがとうございます!!
お疲れ様です! 有益な記事を書いて頂き、ありがとうございます🙏
コード例を読んでいたのですが、一点、誤りと思われる箇所を発見したのでご連絡致します。
となっているのですが、これは
が正しい形だと思われます。
type MyExtract<T, U> = T extends U ? T : never;
と型定義をしているので、"A"
のときは"A" : never
になり、"B"
のときは"B" : never
になるからです。ご確認頂けたら嬉しいです✨
LEF様
typoしておりました、、修正しております!
このようなご指摘いただけるのは大変嬉しいです、ありがとうございます!!