TypeScript PR

【TypeScript】型抽出の魔法「infer」の基本から応用までの使い方11選

【TypeScript】型抽出の魔法「infer」の基本から応用までの使い方11選
記事内に商品プロモーションを含む場合があります

こんにちは!

今、多くの開発現場で使われているTypeScriptの型システム。その中で便利な「infer」について解説します。

TypeScriptのinferって何なの?
条件型とか難しそうで使い方がわからない…
型推論の仕組みがよくわからなくて困っている…

もしかすると、そんな疑問や不安を抱えているかもしれませんね。

この記事では、inferについて、基本的な概念から応用的な使い方までを詳しく解説していきます。

この記事は、以下のような方におすすめです。

この記事はこんな人におすすめ!
  • TypeScriptのinferがどんなものか知りたい方
  • 条件型(Conditional Types)の理解を深めたい方
  • 複雑な型定義をもっとシンプルに書きたい方
  • TypeScriptの型推論の仕組みを使いこなしたい方

この記事を読めば、inferの基本から応用までを理解できるだけでなく、実際に自分のコードに取り入れられるようになるでしょう。さらに、型推論を活用した高度な型定義のテクニックも身につけることができます。

「TypeScriptをもっと深く理解したい!」「inferの使い方をマスターしたい!」と考えているあなたは、ぜひ最後まで読んでください。

それでは、順を追って詳しく見ていきましょう!

inferとは何か?

まずは、TypeScriptにおけるinferとは何か、その基本的な概念を理解しましょう。

inferの役割

inferは、TypeScriptの条件型(Conditional Types)の中で使用され、型の一部を抽出または推論するために使われます。簡単に言えば、「型の中から特定の型情報を取り出す」ための機能です。

このキーワードは直訳すると「推論する」という意味を持ち、その名の通り、TypeScriptの型システムに対して「ここの型を推論して、その結果を使わせて」と指示するものです。

特に、inferは条件型のextendsキーワードと一緒に使われることが多く、「ある型が特定の形式を持っていたら、その一部を抽出する」という処理を可能にします。

条件型とinferの関係

inferを理解するためには、まず条件型について知っておく必要があります。

条件型は、JavaScriptの三項演算子(condition ? trueValue : falseValue)に似た構文を持ち、型レベルで条件分岐を行います。

type Result<T> = T extends string ? 'string型です' : '他の型です';

// 使用例
type Test1 = Result<string>;  // 'string型です'
type Test2 = Result<number>;  // '他の型です'

inferは、この条件型のextends句の中で使われ、条件にマッチした部分の型変数として取り出す役割を果たします。

例えば、配列型からその要素の型を取り出す例を見てみましょう。

type ArrayElementType<T> = T extends Array<infer E> ? E : never;

// 使用例
type StringArray = ArrayElementType<string[]>;  // string
type NumberArray = ArrayElementType<number[]>;  // number

この例では、TArray<何らかの型>という形式を持っていれば、その「何らかの型」をEとして取り出し、そうでなければneverを返します。

なぜinferが必要なのか

TypeScriptで開発をしていると、「ある型から特定の情報を取り出したい」というケースが頻繁に発生します。例えば:

  • 関数の戻り値の型を取得したい
  • オブジェクト型の特定のプロパティの型を取得したい
  • タプル型から特定の位置の要素の型を取得したい

これらの問題は、inferを使わなければ解決が難しかったり、冗長なコードになってしまったりします。inferの登場により、これらの操作がエレガントに書けるようになりました。

inferの基本的な使い方

それでは、inferの具体的な使い方を見ていきましょう。

配列要素の型を取得する

先ほど少し触れましたが、配列から要素の型を取得するのはinferの基本的な使い方です。

type GetArrayElementType<T> = T extends (infer U)[] ? U : never;

// 使用例
type StringType = GetArrayElementType<string[]>;  // string
type NumberType = GetArrayElementType<number[]>;  // number
type MixedType = GetArrayElementType<(string | number)[]>;  // string | number

このコードでは、T何らかの型の配列であれば、その「何らかの型」をUとして取り出しています。

関数の戻り値の型を取得する

関数型からその戻り値の型を取得するのも、inferの代表的な使用例です。

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

// 使用例
function getMessage() {
  return "Hello, TypeScript!";
}

function getCount() {
  return 42;
}

type MessageType = ReturnType<typeof getMessage>;  // string
type CountType = ReturnType<typeof getCount>;      // number

このように、関数型Tから戻り値の型Rを抽出できます。実は、このような便利な型はTypeScriptに標準で組み込まれていて、ReturnTypeという名前でまさにこの機能を提供しています。

関数の引数の型を取得する

関数の引数の型を取得することも可能です。

type ParamType<T> = T extends (param: infer P) => any ? P : never;

// 使用例
function greet(name: string) {
  return `Hello, ${name}!`;
}

function process(value: number) {
  return value * 2;
}

type GreetParam = ParamType<typeof greet>;    // string
type ProcessParam = ParamType<typeof process>; // number

複数の引数を持つ関数の場合は、次のように書くこともできます。

type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;

// 使用例
function combine(str: string, num: number) {
  return str + num;
}

type FirstArgType = FirstParam<typeof combine>;  // string

プロミスの結果型を取得する

Promise型から解決値の型を取得するのもinferの一般的な使い方です。

type PromiseValueType<T> = T extends Promise<infer V> ? V : never;

// 使用例
async function fetchData() {
  return { id: 1, name: "TypeScript" };
}

type DataType = PromiseValueType<ReturnType<typeof fetchData>>;
// { id: number; name: string; }

このように、Promise型からその解決値の型を抽出できます。これも標準でAwaitedという型が提供されています。

オブジェクトのプロパティ型を抽出する

オブジェクトの特定のプロパティの型を抽出するのにもinferが役立ちます。

type PropertyType<T, K extends keyof T> = T[K];

type User = {
  id: number;
  name: string;
  age: number;
};

// 使用例
type IdType = PropertyType<User, 'id'>;    // number
type NameType = PropertyType<User, 'name'>;  // string

実はこの例では直接inferを使っていませんが、TypeScriptの索引型アクセス(indexed access types)を使って同様の機能を実現しています。

inferを使った例も見てみましょう。例えば、オブジェクトの特定のプロパティが存在する場合にその型を抽出する、といったことが可能です。

type ExtractNameProperty<T> = T extends { name: infer N } ? N : never;

// 使用例
type UserName = ExtractNameProperty<User>;  // string
type NoName = ExtractNameProperty<{ id: number }>;  // never

タプルから特定の位置の型を取得する

タプル型から特定の位置の要素の型を抽出することもできます。

type FirstElement<T> = T extends [infer F, ...any[]] ? F : never;
type SecondElement<T> = T extends [any, infer S, ...any[]] ? S : never;

// 使用例
type Tuple = [string, number, boolean];
type First = FirstElement<Tuple>;   // string
type Second = SecondElement<Tuple>; // number

複数の型を一度に抽出する

inferは複数の型を一度に抽出することもできます。

type PairTypes<T> = T extends [infer A, infer B] ? [A, B] : never;

// 使用例
type Pair = PairTypes<[string, number]>;  // [string, number]

さらに複雑な例として、オブジェクトの複数のプロパティの型を同時に抽出することも可能です。

type ExtractUserInfo<T> = T extends { id: infer ID; name: infer Name }
  ? { extractedId: ID; extractedName: Name }
  : never;

// 使用例
type UserInfo = ExtractUserInfo<User>;
// { extractedId: number; extractedName: string }

inferの高度な使い方と応用例

ここからは、inferのより高度な使い方と応用的な応用例を見ていきましょう。

再帰的な型の定義

inferは再帰的な型定義と組み合わせることで、複雑なデータ構造の型も扱えます。

type NestedArrayElement<T> = T extends (infer U)[]
  ? NestedArrayElement<U>
  : T;

// 使用例
type Simple = NestedArrayElement<string>;         // string
type OneLevel = NestedArrayElement<string[]>;     // string
type TwoLevels = NestedArrayElement<string[][]>;  // string

この例では、どんなに深くネストされた配列でも、その最終的な要素の型を取り出すことができます。

型の変換と操作

inferを使って型を抽出した後、その結果を加工することもできます。

type TransformObjectProps<T> = T extends object
  ? { [K in keyof T]: T[K] extends string ? number : T[K] }
  : T;

// 使用例
type UserWithNumberName = TransformObjectProps<User>;
// { id: number; name: number; age: number; }

この例では、オブジェクト型のプロパティのうち、string型のものをnumber型に変換しています。

条件型と共用体型(Union Types)の組み合わせ

inferは条件型と共用体型(Union Types)を組み合わせて使うことで、より柔軟な型操作が可能になります。

type ExtractStringOrNumber<T> = T extends string | number ? T : never;

// 使用例
type ValidType = ExtractStringOrNumber<string | number | boolean>;  // string | number
type InvalidType = ExtractStringOrNumber<boolean>;  // never

より複雑な例として、型の配列から特定の型だけを抽出することもできます。

type FilterTypes<T, U> = T extends U ? T : never;

// 使用例
type NumberOrBoolean = FilterTypes<string | number | boolean, number | boolean>;
// number | boolean

実際のユースケース:APIレスポンスの型定義

実際の開発では、例えばAPIレスポンスの型定義にinferが役立つケースがあります。

type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

type ExtractApiData<T> = T extends ApiResponse<infer D> ? D : never;

// 使用例
type UserResponse = ApiResponse<User>;
type ExtractedUser = ExtractApiData<UserResponse>;  // User

このように、APIのレスポンス型から実際のデータ部分の型だけを抽出することができます。

inferを使ったユーティリティ型

TypeScriptには、inferを活用した便利な組み込みユーティリティ型がいくつか用意されています。これらを理解しておくと、日々のTypeScript開発が格段に便利になります。

ReturnType

ReturnTypeは、関数型からその戻り値の型を抽出するユーティリティ型です。

// 使用例
function fetchUser() {
  return { id: 1, name: "Alice" };
}

type User = ReturnType<typeof fetchUser>;  // { id: number; name: string; }

このユーティリティ型は、APIの戻り値の型を取得したり、別の関数の結果を処理する関数を作成したりする際に非常に役立ちます。

Parameters

Parametersは、関数型からそのパラメータの型をタプルとして取得するユーティリティ型です。

// 使用例
function createUser(id: number, name: string, isPremium: boolean) {
  return { id, name, isPremium };
}

type CreateUserParams = Parameters<typeof createUser>;  // [number, string, boolean]
type FirstParam = CreateUserParams[0];  // number

これは、既存の関数と同じパラメータを受け取る新しい関数を作りたい場合などに便利です。

Awaited

Awaitedは、Promise型からその解決値の型を取得するユーティリティ型です。TypeScript 4.5から導入されました。

// 使用例
async function fetchData() {
  return { success: true, data: [1, 2, 3] };
}

type FetchResult = Awaited<ReturnType<typeof fetchData>>;
// { success: boolean; data: number[] }

ネストされたPromiseも適切に処理できる点が特徴です。これは、非同期処理の結果を型安全に扱いたい場合に非常に役立ちます。

Partial

PartialはTypeScriptの型操作において非常に便利なユーティリティ型です。

// 使用例
interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

Partialは、型のすべてのプロパティをオプショナル(省略可能)にします。これは、更新操作用のオブジェクト型など、一部のプロパティだけを持つオブジェクトを表現する場合に便利です。

function updateUser(userId: number, updates: Partial<User>) {
  // ユーザーを更新する処理
}

// 名前だけを更新
updateUser(1, { name: "Bob" });

これらのユーティリティ型を使いこなすことで、TypeScriptの型システムをより効果的に活用できるようになります。

【付録】さらに学びを深めるためのリソース


intersection型について理解を深めたところで、さらに学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍


プロを目指す人のためのTypeScript入門


プロ開発者への近道!基礎から応用まで幅広くカバー

1一番のおすすめは「プロを目指す人のためのTypeScript入門」です。
この本は、TypeScriptの基礎から高度な使い方まで幅広く解説しています。

初心者でも理解しやすい説明から始まり、徐々に難易度を上げていく構成が特徴。
プログラミング経験者なら、さらに深い理解が得られるでしょう。

実際の開発現場で使えるテクニックも豊富に紹介されているのがポイント。
型の使い方や設計パターンなど、実践的な内容が満載です。

プロの開発者を目指す人はもちろん、TypeScriptをしっかり学びたい人におすすめの一冊。
基礎固めから応用力の向上まで、幅広いニーズに応えてくれます。

現場で使えるTypeScript 詳解実践ガイド


実践的スキルを身につけたい人必見!現場のノウハウが詰まった一冊

次におすすめは「現場で使えるTypeScript 詳解実践ガイド」がランクイン。
この本の特徴は、実際の開発現場で役立つ知識が詰まっていること。

TypeScriptの基本的な文法から始まり、実際のプロジェクトでどう活用するかまで丁寧に解説しています。
エラー対処法や性能改善のコツなど、現場ならではの知恵も満載。

特に、大規模なプロジェクトでTypeScriptを使う際のベストプラクティスが学べるのが魅力。
コードの保守性や再利用性を高める方法も詳しく紹介されています。

すでにJavaScriptの経験がある人や、実務でTypeScriptを使いたい人におすすめ。
この本を読めば、現場で即戦力として活躍できる力が身につくはずです。

書籍に関してはこちらの記事も参考にしてくださいね!




オンラインで参照できる公式ドキュメント


TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。

キャリア形成/給与還元
ひとつひとつ真摯に向き合う企業
ONE_WEDGE社員募集

株式会社 ONE WEDGEでは、新たな仲間を募集しています!

私たちと一緒に、革新的で充実したキャリアを築きませんか?
当社は、従業員が仕事と私生活のバランスを大切にできるよう、充実した福利厚生を整えています。

  • 完全週休2日制(土日休み)で、祝日や夏季休暇、年末年始休暇もしっかり保証!
  • 様々な休暇制度(有給、慶弔、産前・産後、育児、バースデー休暇、有給6日取得で特別休暇付与)を完備!
  • 従業員の成長と健康を支援するための表彰制度、資格取得支援、健康促進手当など!
  • 生活を支えるテレワーク手当、記事寄稿手当、結婚祝金・出産祝金など、様々な手当を提供!
  • 自己啓発としての書籍購入制度や、メンバー間のコミュニケーションを深める交流費補助!
  • 成果に応じた決算賞与や、リファラル採用手当、AI手当など、頑張りをしっかり評価!
  • ワークライフバランスを重視し、副業もOK!

株式会社 ONE WEDGEでは、一人ひとりの従業員が自己実現できる環境を大切にしています。
共に成長し、刺激を与え合える仲間をお待ちしております。
あなたの能力と熱意を、ぜひ当社で発揮してください。
ご応募お待ちしております!

ホームページ、採用情報は下記ボタンからご確認ください!

応募、ご質問など、LINEでお気軽にご相談ください♪

まとめ

ここまで、TypeScriptのinferについて詳しく見てきました。改めて、重要なポイントをおさらいしましょう。

ポイントは以下の通りです。

  • inferは条件型(Conditional Types)の中で使用され、型の一部を抽出・推論するためのキーワードである。
  • 配列要素の型取得、関数の戻り値や引数の型取得、Promiseの結果型取得など、様々な場面で活用できる。
  • オブジェクトのプロパティ型抽出やタプルの特定位置の型取得など、より複雑な型操作も可能。
  • 再帰的な型定義や型の変換・操作など、高度な使い方もある。
  • 実際の開発では、APIレスポンスの型定義などに役立つ。
  • ユーティリティ型を活用し、可読性と保守性のバランスを取りながら使うことが重要。

TypeScriptのinferは、一見難しく見えるかもしれませんが、使いこなせるようになると型定義の幅が大きく広がります。ぜひこの記事を参考に、自分のコードでも活用してみてください!

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です