UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【TypeScript】またinferについて忘れてしまったので Conditional Type をおさらいする

TypeScriptの Conditional Type はJavaScriptでいう三項演算子のような使い方ができるのは理解していてそのように使っているが、それ以上の理解(例えばinferの利用など)は以前調べているにもかかわらず理解が曖昧だった

uga-box.hatenablog.com

なので改めておさらいすることにした

www.typescriptlang.org

かんたんな利用例

例えば以下のような型を用意した時

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の型をそれぞれACにキャプチャして、A & Cの型を定義するConditional Type である

おそらく今度こそ理解できたので、上の定義はたやすく読めるようになった