【TypeScript】ジェネリック関数の戻り値型:詳細解説とサンプルコード
TypeScriptにおけるジェネリック関数の戻り値型
TypeScriptのジェネリック関数とは、型パラメータと呼ばれる抽象的な型を使用して定義される関数です。この型パラメータは、関数が呼び出される際に具体的な型に置き換えられます。ジェネリック関数は、コードの再利用性と型安全性向上に役立ちます。
ジェネリック関数の戻り値型は、型パラメータによって決まります。戻り値の型を明示的に指定することもできますが、TypeScriptコンパイラは多くの場合、型パラメータから戻り値の型を自動的に推論することができます。
例
以下の例は、2つの値を受け取り、そのどちらか一方をランダムに返すジェネリック関数 chooseRandom
を示しています。
function chooseRandom<T>(a: T, b: T): T {
return Math.random() < 0.5 ? a : b;
}
この関数は、T
という型パラメータを持ちます。この型パラメータは、a
と b
の引数の型、および関数の戻り値の型を表します。
chooseRandom
関数は、以下のようないくつかの方法で呼び出すことができます。
const num1 = chooseRandom(10, 20); // num1 は number 型
const str1 = chooseRandom('Hello', 'World'); // str1 は string 型
TypeScriptコンパイラは、関数が呼び出される際に、T
型パラメータを具体的な型に置き換えます。上記の例では、num1
は number
型、str1
は string
型になります。
戻り値の型を明示的に指定する
ジェネリック関数の戻り値の型を明示的に指定することもできます。これは、型推論がうまくいかない場合や、より明確な型情報を提供したい場合に役立ちます。
function chooseRandomWithExplicitReturnType<T>(a: T, b: T): T extends string ? string : number {
return Math.random() < 0.5 ? a : b;
}
この例では、chooseRandomWithExplicitReturnType
関数の戻り値の型は、a
と b
の引数の型がどちらも string
型である場合に string
型、そうでない場合は number
型となります。
ジェネリック関数の利点
ジェネリック関数を使用する利点は次のとおりです。
- 型安全性: ジェネリック関数は、コンパイラが型エラーを検出するのに役立ちます。これにより、コードのバグを防ぎ、より堅牢なプログラムを作成することができます。
- コードの再利用性: ジェネリック関数は、さまざまな型のデータに対して同じロジックを再利用できるようにします。これにより、コードの重複を減らし、保守性を向上させることができます。
基本的な例
function chooseRandom<T>(a: T, b: T): T {
return Math.random() < 0.5 ? a : b;
}
const num1 = chooseRandom(10, 20); // num1 は number 型
const str1 = chooseRandom('Hello', 'World'); // str1 は string 型
解説
chooseRandom
関数は、さまざまな型のデータに対して呼び出すことができます。呼び出し時に渡される引数の型によって、戻り値の型が決まります。- 関数内では、
Math.random()
関数を使用してランダムな値を生成し、その値に基づいてa
またはb
のいずれかを返します。
この例では、chooseRandomWithExplicitReturnType
関数は、戻り値の型を明示的に指定します。
function chooseRandomWithExplicitReturnType<T>(a: T, b: T): T extends string ? string : number {
return Math.random() < 0.5 ? a : b;
}
const num2 = chooseRandomWithExplicitReturnType(10, 20); // num2 は number 型
const str2 = chooseRandomWithExplicitReturnType('Hello', 10); // エラーが発生
str2
変数には、chooseRandomWithExplicitReturnType
関数をstring
型とnumber
型の引数で呼び出した結果が代入されます。これは型エラーとなり、コンパイル時にエラーが発生します。
ジェネリック型を使用したクラス
この例では、ジェネリック型を使用して Box
クラスを定義します。このクラスは、指定された型の値を保持するボックスを表します。
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
const stringBox = new Box<string>('Hello');
const numberBox = new Box<number>(10);
numberBox
変数は、number
型の値を保持するBox
オブジェクトを表します。setValue
メソッドは、ボックスに保持されている値を更新するために使用されます。constructor
メソッドは、ボックスに保持される初期値を指定するために使用されます。Box
クラスは、T
という型パラメータを持ちます。この型パラメータは、ボックスに保持される値の型を表します。
型推論を利用する
TypeScriptコンパイラは、多くの場合、ジェネリック関数の戻り値の型を自動的に推論することができます。これは、関数の引数と使用箇所に基づいて型を推定するためです。
function identity<T>(value: T): T {
return value;
}
const num = identity(10); // num は number 型
const str = identity('Hello'); // str は string 型
上記の例では、identity
関数は、引数と同じ型の値を返します。コンパイラは、num
変数に number
型、str
変数に string
型を自動的に推論します。
ジェネリック型パラメータを拡張する
より複雑な型推論が必要な場合は、ジェネリック型パラメータを拡張することができます。これにより、コンパイラに提供する型情報が増え、より正確な推論が可能になります。
interface Data<T> {
value: T;
}
function getValueFromData<T>(data: Data<T>): T {
return data.value;
}
const data1: Data<number> = { value: 10 };
const num3 = getValueFromData(data1); // num3 は number 型
const data2: Data<string> = { value: 'Hello' };
const str3 = getValueFromData(data2); // str3 は string 型
上記の例では、Data
インターフェースは、value
プロパティを持つジェネリック型パラメータ T
を定義します。getValueFromData
関数は、Data
オブジェクトを受け取り、その value
プロパティを返します。コンパイラは、data1
と data2
変数に渡される Data
オブジェクトの型に基づいて、num3
と str3
変数の型を推論します。
型ガードを使用する
ジェネリック関数の戻り値の型が、より複雑なロジックによって決まる場合もあります。このような場合は、型ガードを使用して、コンパイラに型情報を提供することができます。
function firstOrLast<T>(arr: T[], index: number): T | undefined {
if (index < 0 || index >= arr.length) {
return undefined;
}
return arr[index];
}
const arr1 = [1, 2, 3];
const num4 = firstOrLast(arr1, 0); // num4 は number 型
const num5 = firstOrLast(arr1, 3); // num5 は number 型 | undefined
上記の例では、firstOrLast
関数は、配列の最初の要素または最後の要素を返します。index
引数は、要素を取得するインデックスを指定します。コンパイラは、num4
変数に number
型を推論しますが、num5
変数には number
型または undefined
型のいずれかを推論します。これは、index
が配列の長さよりも大きいか小さい場合、関数が undefined
を返すためです。
ジェネリック関数の戻り値型を扱うには、さまざまな方法があります。状況に応じて適切な方法を選択することが重要です。
- 型ガード: 戻り値の型が複雑なロジックによって決まる場合に役立ちます。
- ジェネリック型パラメータの拡張: より複雑な型推論が必要な場合に役立ちます。
- 型推論: 最も簡潔で、多くの場合十分です。
typescript generics