【TypeScript】inferキーワードの10の魅力的な使い方

TypeScriptのinferキーワードを解説するイラスト付きの記事サムネイルTypeScript
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptはJavaScriptに静的型チェックを追加するスーパーセットとして非常に人気を集めています。

その中でも、inferキーワードはTypeScriptを使う上で非常に便利な機能として知られています。

このキーワードの魅力を最大限に引き出すための方法を、10の具体的なコード例と共に紹介します。

こちらの記事を通じて、初心者でもinferキーワードの力を十分に活用する方法を習得できるでしょう。

●TypeScriptの基礎知識

TypeScriptは、JavaScriptのスーパーセットとして開発された言語であり、コードの安全性とスケーラビリティを大幅に向上させることができます。

静的型チェックの恩恵を受けることで、バグの早期発見やコードのリファクタリングが容易になります。

○TypeScriptの特徴となぜ学ぶべきか

□型システム

TypeScriptの最大の特徴は静的型チェックです。

型注釈を使用して変数や関数の引数、戻り値の型を明示的に指定することができます。

let name: string = "Taro";
function greet(person: string): string {
  return "Hello, " + person;
}

このコードでは、変数nameは文字列型として宣言され、greet関数は文字列を引数に取り、文字列を返す関数として定義されています。

□型推論

型注釈を使用しない場合でも、TypeScriptはコードから適切な型を推論する能力を持っています。

let age = 30; // number型として推論される

このコードの場合、30は数字なので、ageは自動的にnumber型として推論されます。

□高度な型機能

TypeScriptは、ジェネリック、インタセクション、ユニオン、リテラル型など、多くの高度な型機能を持っています。

これにより、非常に柔軟かつ強力な型制御が可能となります。

学ぶべき理由は、これら3つの特徴を活用することで、コードの品質を向上させ、大規模なプロジェクトでも安心して開発を進めることができるからです。

特にinferキーワードは、TypeScriptの高度な型機能の中でも非常に有用であり、多くの場面で役立つ機能です。

●inferキーワードの基本

TypeScriptは静的型チェック言語として、JavaScriptの上に強力な型システムを持つ特徴を持っています。

この型システムの中でも、inferキーワードは非常に強力で、型推論の能力を大幅に拡張してくれるものです。

ここでは、inferキーワードの基本的な特性とその役割について、詳しく掘り下げていきます。

○inferとは何か:シンプルな解説

inferキーワードは、TypeScriptの型システム内で型推論を行う際に使用されるキーワードです。

具体的には、ジェネリック型の中で、ある型が未知の場合にその型を「推論」し、「一時的」に名前をつけることができます。

例を挙げると、次のようなシンプルな関数があったとします。

function identity<T>(arg: T): T {
    return arg;
}

上記は、どんな型でも受け取り、そのまま返す関数です。

この関数を使用する時、Tという型は関数に渡される引数によって「推論」されます。

例えば、identity<number>(5)とすれば、Tnumberと推論されます。

inferを使用すると、型の中で新しい型を推論し、その型に一時的に名前をつけることができます。

この機能は、特に関数の戻り値など、複雑な型操作が必要な場面で非常に役立ちます。

○inferの具体的な役割と利点

inferキーワードの一番の利点は、型情報が完全に不明な状態でも、その型を一時的に「キャッチ」して操作できる点です。

具体的には、条件型やジェネリック型の中で、戻り値やオブジェクトのプロパティ、関数の引数など、さまざまな場面での型を推論するのに役立ちます。

また、inferは型安全を維持しながら、動的な型操作を実現します。

これにより、TypeScriptを使用する開発者は、より柔軟な型操作を行いつつ、コードの安全性を確保することができます。

例えば、ある関数の戻り値の型を取得したい場合に、ReturnTypeという組み込みの型がTypeScriptには存在します。

この型の内部ではinferキーワードが使用されており、次のように定義されています。

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

このコードでは、Tという型が関数である場合、その関数の戻り値の型をRとして推論します。

そして、そのRReturnTypeの結果として得られます。

●実践的なinferの使い方:10のコード例

TypeScriptには強力な型推論機能が備わっており、その中でもinferキーワードは非常に魅力的なツールとなっています。

この記事では、TypeScriptのinferキーワードを使用した10の実践的なコード例を通じて、その魅力や活用方法を解説していきます。

○サンプルコード1:基本的な型推論

初めに、inferキーワードを使った基本的な型推論の例を見てみましょう。

inferは、条件型の内部で新しい型変数を導入するためのキーワードです。

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

このコードでは、関数型Tが引数をとり、その戻り値型をRとして推論します。

そして、条件型を使用して、関数であればその戻り値型Rを、そうでなければnever型を返します。

例として、次の関数sampleFunctionを考えます。

function sampleFunction(a: number, b: number): string {
  return `${a + b}`;
}

上記のReturnTypeOf型を使用して、この関数の戻り値型を推論することができます。

type SampleFunctionReturnType = ReturnTypeOf<typeof sampleFunction>;
// SampleFunctionReturnTypeはstring型になります

このコードを実行すると、SampleFunctionReturnTypesampleFunctionの戻り値としてのstring型を持つことになります。

これは、関数sampleFunctionが数値を受け取り、文字列を返すためです。

○サンプルコード2:関数の戻り値型を推論する

TypeScriptの強力な機能の1つに「型推論」があります。

型推論を使うことで、開発者は明示的に型を指定しなくても、TypeScriptが型を自動的に理解してくれます。

これは、コードの品質を向上させるだけでなく、エラーやバグの可能性を減少させる大きな利点となります。

ここでは、関数の戻り値の型を推論する方法について、inferキーワードを使用して詳しく見ていきます。

関数の戻り値の型を推論するためのサンプルコードを紹介します。

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

function sampleFunction(name: string): { name: string } {
    return { name };
}

type SampleType = ReturnTypeOf<typeof sampleFunction>;

このコードでは、ジェネリック型ReturnTypeOfを使って、関数の戻り値の型を効果的に取得します。

このジェネリック型は、Tが関数であるかどうかを確認し、関数である場合、その関数の戻り値の型をRとして推論します。

まずジェネリック型ReturnTypeOfを定義しています。

この型は、Tというジェネリックパラメータを受け取り、Tが関数型である場合、その関数の戻り値の型をRとして推論します。

そして、この推論された型Rを結果として返します。もしTが関数型でない場合は、never型を返します。

次に、sampleFunctionという関数を定義しています。

この関数は、文字列型の引数nameを受け取り、{ name: string }型のオブジェクトを返します。

最後に、ReturnTypeOf型を使って、sampleFunctionの戻り値の型をSampleTypeとして取得しています。

このコードを実行すると、SampleType{ name: string }型として推論されるので、TypeScriptの型推論の魅力を実感することができます。

○サンプルコード3:オブジェクトのプロパティを推論する

TypeScriptにおいて、inferキーワードは非常に強力なツールとなっています。

ここでは、オブジェクトのプロパティを推論する際のinferの活用法について、実際のコードをもとに解説します。

まず、オブジェクトの型を推論する際の基本的な方法について考えてみましょう。

例えば、次のようなオブジェクトがあった場合、そのプロパティの型を取得することが考えられます。

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

このコードでは、Userという型が定義されており、nameageの2つのプロパティを持っています。

では、このオブジェクトのプロパティの型を推論する方法を考えてみましょう。

inferを用いれば、オブジェクトのプロパティの型を効果的に推論することが可能となります。

下記のコードは、オブジェクトのプロパティnameの型を推論するものです。

type PropertyType<T> = T extends { name: infer U } ? U : never;

type UserNameType = PropertyType<User>;  // UserNameTypeはstring型として推論される

このコードでは、PropertyTypeというジェネリック型を定義しています。

この型は、引数として与えられた型Tnameプロパティの型を推論し、その型をUとして取得します。

もしTnameプロパティを持っていない場合、never型となります。

実際に、User型をPropertyTypeの型引数として与えると、UserNameTypestring型として推論されます。

このように、inferを活用することで、簡単にオブジェクトのプロパティの型を取得することができます。

このような型推論の手法は、大規模なプロジェクトやライブラリの開発において、型の安全性を保ちつつも柔軟なコードを記述するための強力なツールとして利用されることが多いです。

このコードを実行すると、特に出力はされませんが、TypeScriptの型システム上で、UserNameTypestring型として正しく推論されることが確認できます。

○サンプルコード4:ジェネリック型での使用例

TypeScriptの世界で、ジェネリックは非常に強力なツールです。

ジェネリックを用いることで、型を柔軟に扱い、再利用性の高いコードを書くことができます。

ここでは、inferキーワードを使ったジェネリック型のサンプルコードを解説します。

type ExtractArrayType<T> = T extends Array<infer U> ? U : never;

このコードでは、ジェネリック型Tを受け取り、その型が配列である場合、その配列の要素の型を返すExtractArrayTypeという型を定義しています。

ここでのポイントはinfer U部分です。

この部分はTが配列である場合、その配列の要素の型をUとして推論する役割を果たします。

Uはこの場面でのみ有効なローカルな型変数となります。

実際にこの型を使ってみると、次のようなコードが考えられます。

type NumberArray = ExtractArrayType<number[]>;
type StringArray = ExtractArrayType<string[]>;

このコードを実行すると、NumberArrayの型はnumberに、StringArrayの型はstringになります。

つまり、それぞれの配列の要素の型を正しく推論して、新しい型として利用できるのです。

次に、この型が非配列の場合の挙動を確認してみましょう。

type NonArraySample = ExtractArrayType<number>;

この場合、NonArraySampleの型はneverとなります。

なぜなら、ExtractArrayTypeは非配列の型を受け取ると、never型を返すように定義されているからです。

○サンプルコード5:ネストしたオブジェクトの型を推論する

TypeScriptの型システムをより深く探る上で、ネストしたオブジェクトの型推論は避けて通れないテーマと言えるでしょう。

特に、大規模なプロジェクトや複雑なデータ構造を持つ場合、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>;

このコードでは、NestedObjectTypeというジェネリック型を定義しています。

このジェネリック型は、オブジェクトがab.cという2つのプロパティを持っているかどうかを確認し、その型をAとCとして捉え、最終的にA & Cの合成型を返すように設計されています。

具体的には、sampleObjというオブジェクトをNestedObjectTypeに渡すことで、ResultTypeという型を得られます。

この場合、sampleObjaというプロパティに文字列を、b.cというプロパティに数値を持っているため、ResultTypestring & numberという型になります。

このように、inferキーワードを使用することでネストしたオブジェクトの中の型を簡単に取り出して再利用することが可能となります。

この例を参考にすると、inferキーワードを用いてネストしたオブジェクトの内部の型情報を取得・利用する手法の一端を垣間見ることができます。

もちろん、もっと複雑なデータ構造や異なる用途に応じて、この手法をさまざまに応用することができます。

ここでの実行結果として、ResultTypestring & numberという型になるわけですが、これは文字列と数値の両方の特性を持つ型という意味になります。

ただし、実際にはこの型を持つ値は存在しないので、実際のコード実行時には利用に注意が必要です。

このような複合型は、型レベルでの操作や制約を行う際に非常に役立ちますが、実際の値としての利用は難しいことが多いです。

○サンプルコード6:条件型と組み合わせる

TypeScriptでは、型システムの柔軟性と強力さをさらに引き出すために、inferキーワードを条件型と組み合わせて使用することができます。

条件型とは、ある型が別の型に一致するかどうかを確認し、その結果に基づいて型を割り当てることができる特殊な型です。

下記のサンプルコードは、条件型とinferキーワードを組み合わせて、関数の引数が配列であるかどうかを判定し、配列であればその要素の型を取得する型関数を表しています。

type ElementType<T> = T extends (infer E)[] ? E : never;

const arr = [1, 2, 3];
type ArrType = ElementType<typeof arr>; // number

このコードでは、ElementTypeという型関数を定義しています。

この関数は、与えられた型Tが配列であるかどうかを判定します。

もしTが配列であれば、その要素の型Einferキーワードを使って取得し、Eとして返します。

配列でなければ、never型を返します。

例として、数値の配列arrを定義し、ElementType型関数を使用してその要素の型を取得する方法を表しています。

この場合、ArrTypenumber型として推論されます。

このコードを実行すると、arrの要素は数値なので、ArrTypeは数値型、すなわちnumber型として推論される結果になります。

○サンプルコード7:配列の型を推論する

TypeScriptにおける型推論の魅力的な側面として、配列の要素の型を推論することが挙げられます。

例えば、JavaScriptでは配列内の要素の型を知ることは困難ですが、TypeScriptを用いれば、ジェネリックやinferキーワードの力を借りて、簡単にその型を把握することができます。

下記のサンプルコードは、配列の要素の型を推論するシンプルな例です。

type ElementOf<T> = T extends (infer E)[] ? E : never;

// 使用例
const numbers = [1, 2, 3];
type NumbersType = ElementOf<typeof numbers>;  // numberという型が推論される

このコードではElementOfという型を定義しています。

ジェネリック型Tが配列である場合、inferキーワードを使ってその配列の要素の型をEとして推論し、その型を結果として返しています。

配列でない場合はnever型を返します。

使用例として、numbersという数値の配列を定義し、その要素の型をElementOfを用いてNumbersTypeとして推論しています。

結果、NumbersTypenumber型として推論されます。

実際にこのコードをTypeScript環境で実行すると、numbersの要素は数値であることから、NumbersTypeはnumber型として正しく推論されます。

これにより、配列の要素の型を動的に取得することが可能となり、柔軟かつ型安全なコードを書くことができます。

この方法は、APIから取得したデータのように、事前に型が不明確な配列の要素の型を特定したい場合や、ライブラリやフレームワークの内部で型情報を保持するためのユーティリティとしても非常に有効です。

続けて、配列だけでなく、タプルの要素の型を推論する応用例を考えてみましょう。

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

// 使用例
const tuple = [1, "two", true];
type FirstType = FirstElementOf<typeof tuple>;  // numberという型が推論される

このコードでは、タプルの最初の要素の型を推論するFirstElementOfという型を定義しています。

同様にinferキーワードを使用しており、タプルの最初の要素の型をFとして推論しています。

使用例として、異なる型の要素を持つタプルを定義し、その最初の要素の型をFirstElementOfを用いてFirstTypeとして推論しています。

結果、FirstTypenumber型として推論されます。

このコードを実行すると、タプルtupleの最初の要素は数値の1であるため、FirstTypeはnumber型として正しく推論されます。

これにより、タプルの特定の位置の要素の型を動的に取得することも可能となります。

○サンプルコード8:プロミスの戻り値を推論する

TypeScriptのinferキーワードは、さまざまな場面で型を推論する際に非常に役立ちます。

中でもプロミスの戻り値の型を効果的に推論することが可能です。

通常、非同期処理の結果を待ってからその結果の型を取得したい場面があります。

ここでは、inferを使用してプロミスの戻り値の型を効果的に取得する方法について解説します。

まず、次のサンプルコードをご覧ください。

type PromiseReturnType<T> = T extends Promise<infer U> ? U : never;

async function fetchData(): Promise<number> {
    return 10;
}

type Data = PromiseReturnType<ReturnType<typeof fetchData>>;

このコードでは、まずPromiseReturnTypeという型を定義しています。

この型はジェネリック型Tを受け取り、TPromise<何らかの型>である場合、その何らかの型infer Uを使用して推論し、その型Uを返します。

もしTがプロミスでない場合、never型を返します。

続いて、fetchDataという非同期関数を定義しています。

この関数は数値の10を返すプロミスを返します。

この関数の戻り値の型を取得したい場合、ReturnType<typeof fetchData>を使用することで、Promise<number>という型を取得できます。

しかし、実際にはnumber型だけを取得したい場面も多いです。

そこで先ほど定義したPromiseReturnType型を利用します。

type Data = PromiseReturnType<ReturnType<typeof fetchData>>;の行では、fetchData関数の戻り値の型をPromiseReturnType型に渡しています。

その結果、Data型はnumber型となります。

このコードを実行すると、特定の非同期関数の戻り値の型を効果的に推論することができます。

このように、非同期処理を行う際の型安全性を向上させるために、inferキーワードを活用することが推奨されます。

○サンプルコード9:独自の型関数を作成する

TypeScriptを用いてプログラムを記述する中で、より柔軟で強力な型の表現を手に入れるために、独自の型関数を作成する方法があります。

その際、inferキーワードの力を借りることで、型関数の中で一時的な型を宣言し、それを利用して型推論を行うことができます。

今回は、その魅力的な使い方を、サンプルコードを通して具体的に解説します。

type Flatten<T> = T extends Array<infer U> ? U : T;

// 使用例
type Result = Flatten<number[]>;  // Resultはnumberとして推論される

このコードでは、配列の要素の型を取得するためのFlattenという型関数を定義しています。

具体的には、型Tが配列である場合、その配列の要素の型をinfer Uで一時的に捉え、それを結果として返しています。

もしTが配列でない場合、T自体がそのまま結果として返されます。

このコードを実行すると、Result型はnumberとして推論されます。

というのも、Flatten<number[]>という型は、number[]が配列であるため、その要素の型numberが結果として得られるからです。

この例から、inferキーワードを使って型関数内での型推論を行うことで、複雑な型操作や、型情報の抽出などを行うことが可能であることがわかります。

これはTypeScriptのジェネリック型を強化するための強力なツールと言えるでしょう。

次に、このFlatten型関数を利用して、実際に複数の型に適用する例を見てみましょう。

type A = Flatten<string[]>;      // string
type B = Flatten<number>;        // number
type C = Flatten<Array<boolean>>; // boolean

このコードを実行すると、各型A, B, Cはそれぞれstring, number, booleanとして推論されます。

このように、独自の型関数を作成することで、複数の場所で再利用可能な型操作を行うことができるのです。

○サンプルコード10:ジェネリック制約とinferの組み合わせ

TypeScriptでは、inferキーワードとジェネリック制約の組み合わせで、より複雑な型の推論を実現することができます。

この組み合わせは、特定の条件下での型の推論を可能にします。

今回は、ジェネリック制約とinferを組み合わせた実践的なサンプルコードを通じて、その活用方法を細かく解説していきます。

まず、次のサンプルコードをご覧ください。

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

function sampleFunction(a: number, b: number): string {
  return (a + b).toString();
}

type ReturnTypeOfSampleFunction = ExtractFunctionReturnType<typeof sampleFunction>;

このコードでは、ExtractFunctionReturnTypeという型を定義しています。

この型は、与えられた型Tが関数である場合、その関数の戻り値の型を推論して取り出します。

ジェネリック制約T extends (...args: any[]) => infer Rは、Tが関数であるかをチェックし、関数の場合はその戻り値の型をRとして推論します。

Rは、inferキーワードを使って推論された型となります。

また、sampleFunctionは2つの数値を受け取り、それらの合計値を文字列として返す関数です。

ReturnTypeOfSampleFunctionは、sampleFunctionの戻り値の型をExtractFunctionReturnTypeを使って取得しています。

このコードを実行すると、ReturnTypeOfSampleFunctionの型はstringとなります。

つまり、ExtractFunctionReturnType型を使用することで、sampleFunction関数の戻り値の型を正確に推論して取得することができました。

●inferの注意点とよくある問題点

TypeScriptを学び、実践的なコードを書く中で、inferキーワードは非常に強力なツールですが、それにはいくつかの注意点やよくある問題点が存在します。

これらの問題点を理解し、適切に対応することで、より安全で効率的なTypeScriptのコードを書くことができます。

○初心者が陥りやすい罠

□過度な使用

TypeScriptのinferは非常に強力ですが、その力を過度に使用することは、コードの可読性や保守性を低下させる可能性があります。

inferを使用する場面を選び、必要ない場面では使用しないようにしましょう。

□型推論の誤解

inferを用いた場合、型の推論が常に期待通りに行われるわけではありません。

特に複雑なジェネリック型や条件型を使用する際には、推論される型が意図したものであるか常に確認することが大切です。

型推論の誤解を引き起こす可能性のあるコード例を紹介します。

type MyType<T> = T extends Array<infer U> ? U : never;
const result: MyType<string> = "hello"; // Error!

このコードでは、MyTypeはジェネリック型Tが配列である場合、その配列の要素の型を推論しようとしています。

しかし、string型をTとして渡した場合、この型はneverとなり、resultの型としては不適切です。

○エラー対応のヒント

□エラーメッセージをしっかり読む

TypeScriptのエラーメッセージは非常に詳細で、その内容をしっかりと読むことで、エラーの原因や対応方法がわかりやすくなります。

□型を明示的に指定する

型推論に頼り過ぎず、必要な場面では型を明示的に指定することで、予期せぬエラーを避けることができます。

特に、外部ライブラリやサードパーティのコードを使用する際には、型を明示的に指定することをおすすめします。

□小さな単位でコードを書き、頻繁に型チェックを行う

コードの変更や追加を行う際に、小さな単位で実装を進め、頻繁に型チェックを行うことで、エラーの発生源を早期に特定しやすくなります。

まとめ

TypeScriptは近年のフロントエンド開発における主要な言語として急速に普及しています。

そして、TypeScriptを効果的に利用するためには、その持つ機能やキーワードの理解が必要不可欠です。

今回は、その中でも特に強力なinferキーワードに焦点を当て、その魅力や実践的な使い方を紹介しました。

TypeScriptやinferキーワードの学習は、一度で完璧に理解するのは難しいかもしれません。

しかし、日々の開発を通じて、少しずつ理解を深めていくことが大切です。

ぜひ、今回の記事を手始めに、TypeScriptの魅力を十分に活用してみてください。