はじめに
こんにちは。フロントエンド開発課に所属している新卒1年目のm_you_sanと申します。
今回はTypeScriptのinferについて紹介したいと思います。
inferとは?
inferは型推論する際に使われるキーワードで、ジェネリクス型と条件型(Conditional Types)と合わせて使われます。
inferを使うことで、関数の戻り値や配列の中身など、ジェネリクス型の内容によって変化する型情報をConditional Typesの条件分岐の中で推論することができます。
具体的な使用例
関数の戻り値の型を推論する
inferは組み込み型のReturnTypeの内部で実は使われています。
型変数のR
が、型推論が行われる部分です。
下記のコードでは、ジェネリクス型のT
が関数である場合、その戻り値の型をR
として推論し、そのR
がReturnTypeの結果として得られます。
関数がジェネリクス型である場合、戻り値は文字列や数値などの様々なパターンがありますが、inferを使うことで柔軟に型情報を取得することができます。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; const helloFn = () => "Hello World"; const addtion = (num1: number, num2: number) => num1 + num2; const isEvenNumber = (num: number) => num % 2 === 0; //string型になる type HelloFnReturnType = ReturnType<typeof helloFn>; //number型になる type AddtionReturnType = ReturnType<typeof addtion>; //boolean型になる type IsEvenNumberReturnType = ReturnType<typeof isEvenNumber>;
Promiseの内部の型を推論する
inferを使うことで、Promise型が内包している型を推論することができます。
下記のコードでは、ジェネリクス型のT
がPromise型であった場合、Promiseの型引数の部分をR
として推論して、そのR
がExtractPromiseの結果として得られます。
type ExtractPromise<T> = T extends Promise<infer R> ? R : never; //string型になる type SampleStringType = ExtractPromise<Promise<string>>; //number型になる type SampleNumberType = ExtractPromise<Promise<number>>;
配列の中身を推論する
inferを使うことで、配列の中身を推論することもできます。
下記のコードのFisrtは、ジェネリクス型のT
が配列の型だった場合、その配列の先頭の要素をR
として推論します。
Array1の場合、先頭の要素以外(2と3)は...any[]
となって、1のみがR
として推論されるため、結果としてArray1FirstTypeは1となります。
type Fisrt<T extends any[]> = T extends [infer R, ...any[]] ? R : never; type Array1 = [1, 2, 3]; type Array2 = ["1", 2, 3]; //1になる type Array1FirstType = Fisrt<Array1>; //"1"になる type Array2FirstType = Fisrt<Array2>;
文字列リテラルと組み合わせる
inferは文字列リテラルと組み合わせることができます。
Replaceは文字列S
に含まれる文字列T
をU
に置き換える型です。
具体的な処理の流れとしては、まず初めにstring型のS
にT
が含まれているかをConditional Typesを用いてチェックしています。 このとき、T
の前後の文字列をそれぞれA
、B
として推論しています。
最後にT
をU
に置き換えて、推論されたA
とB
を文字列リテラルで結合させて、結果として返します。
下記の例では、A
は「NO」、B
は「NO LIFE」になり、T
の「MONEY」がU
の「CAT」に置き換わるため、結果として「NO CAT NO LIFE」が得られます。
type Replace< S extends string, T extends string, U extends string > = S extends `${infer A}${T}${infer B}` ? `${A}${U}${B}` : never; //NO CAT NO LIFEになる type replaced = Replace<" NO MONEY NO LIFE", "MONEY", "CAT">;
まとめ
今回はtypescriptのinferについて紹介させていただきました。
個人的には、関数の戻り値やPromiseの型引数など、型情報が不確定なものに対しても、inferを使うことで柔軟に型情報を取得できるところが魅力的に感じました。
inferの使い方について、更に深堀したい方はtype-challengesに取り組んでみるのが良いかと思います。