RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【TypeScript】inferで型情報を取得する

はじめに

こんにちは。フロントエンド開発課に所属している新卒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に含まれる文字列TUに置き換える型です。
具体的な処理の流れとしては、まず初めにstring型のSTが含まれているかをConditional Typesを用いてチェックしています。 このとき、Tの前後の文字列をそれぞれABとして推論しています。 最後にTUに置き換えて、推論されたABを文字列リテラルで結合させて、結果として返します。

下記の例では、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に取り組んでみるのが良いかと思います。

Copyright © RAKUS Co., Ltd. All rights reserved.