TypeScriptの Conditional Type はJavaScriptでいう三項演算子のような使い方ができるのは理解していてそのように使っているが、それ以上の理解(例えばinferの利用など)は以前調べているにもかかわらず理解が曖昧だった
なので改めておさらいすることにした
かんたんな利用例
例えば以下のような型を用意した時
type Cat = { meows: true }; type Dog = { barks: true }; type Cheetah = { meows: true; fast: true }; type Wolf = { barks: true; howls: true };
以下のように吠える(barks)動物の型だけを抜き出すConditional Typesを作ることができる
type ExtractDogish<A> = A extends { barks: true } ? A : never;
これはneverを含むユニオン型は、neverを除くユニオン型になる特性を利用している
ExtractDogish<Cat> | ExtractDogish<Dog> | ExtractDogish<Cheetah> | ExtractDogish<Wolf> = never | Dog | never | Wolf = Dog | Wolf
infer
Conditional Type は、他にも以下のようなFlatten型を作ると配列型の要素型に平坦化できる
type Flatten<T> = T extends any[] ? T[number] : T; // Extracts out the element type. type Str = Flatten<string[]>; type Str = string // Leaves the type alone. type Num = Flatten<number>; type Num = number
この時、inferという機能を使っても書ける
type Flatten<T> = Type extends (infer U)[] ? U : T;
infer の良い点は、こうすることで「配列の要素だからnumber型の要素タイプを定義しなければならない」という詳細を知る必要がなくなる
inferは他にも以下のように関数の戻り値の型を推論して定義させることもできる
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
つまり、inferは Conditional Types の構文の中で型をキャプチャする機能 である
また忘れそうなので、自分なりの解釈を書くと↓
T extends (ここの型の何かしらの型をinferで"A"という変数名でキャプチャする) ? (Aを使って何かできる) : never
inferは複数回利用できる
type NestedObjectType<T> = T extends { a: infer A, b: { c: infer C } } ? A & C : never; const sampleObj = { a: 'hello', b: { c: 123 } }; type ResultType = NestedObjectType<typeof sampleObj>;
上はオブジェクトのプロパティaの型と、プロパティbのプロパティcの型をそれぞれA
とC
にキャプチャして、A & C
の型を定義するConditional Type である
おそらく今度こそ理解できたので、上の定義はたやすく読めるようになった