TypeScriptにおけるジェネリクス

TypeScriptにおけるジェネリクス

この記事ではTypeScriptにおけるジェネリクスについて説明します。

YouTube Video

TypeScriptにおけるジェネリクス

TypeScriptにおけるジェネリクス(Generics)は、型をパラメータ化することで、再利用可能で型安全な関数やクラス、インターフェースを定義するための機能です。ジェネリクスを使用すると、特定の型に依存しないコードを記述でき、様々な型に対して同じ処理を行うことが可能になります。

ジェネリクスの基本

ジェネリクスは、型を引数として受け取るテンプレートのようなもので、関数やクラスが異なる型に対応できるようにします。

ジェネリック関数

以下は、引数の型をジェネリクスで指定した関数の例です。

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity<number>(42));       // 42
6console.log(identity<string>("Hello"));  // Hello
  • T はジェネリック型引数で、関数の引数と戻り値の型を表します。関数が呼び出されるときに、実際の型が決定されます。
  • <number><string> を明示することで、型を指定しています。

ジェネリック型は、TypeScriptが型推論を行うため、明示的に指定しなくても動作します。

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity(42));       // 42
6console.log(identity("Hello"));  // Hello
  • <number><string> と明示しなくても、型推論が行われます。identity(42)numberidentity("Hello")string として推論されます。

ジェネリック型の制約

ジェネリクスに制約をつけることで、特定の型だけを受け入れるように制限できます。

1function loggingIdentity<T extends { length: number }>(arg: T): T {
2    console.log(arg.length);
3    return arg;
4}
5
6loggingIdentity("Hello");  // 5
7loggingIdentity([1, 2, 3]);  // 3
8
9// loggingIdentity(42);  // Error: number does not have a length property.
  • T extends { length: number } として、Tlength プロパティを持つ型であることを指定しています。これにより、length プロパティを持たない型は受け入れられなくなります。

keyofとの併用

ジェネリクスとkeyofを組み合わせれば、プロパティ名も型安全に取得できます。

1function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
2  return obj[key];
3}
4
5const person = { name: "Bob", age: 30 };
6const personName = getProperty(person, "name"); // string
7console.log(personName);
8
9// const error = getProperty(person, "unknown"); // Error
  • Kkeyof T により、T に存在するキーだけを指定できることを示します。存在しないキーを指定した際は、コンパイルエラーとなります。

ジェネリッククラス

クラスもジェネリクスを使用して定義することができます。ジェネリッククラスは、プロパティやメソッドに対して柔軟な型を提供します。

 1class Box<T> {
 2    private _value: T;
 3
 4    constructor(value: T) {
 5        this._value = value;
 6    }
 7
 8    public getValue(): T {
 9        return this._value;
10    }
11
12    public setValue(value: T): void {
13        this._value = value;
14    }
15}
16
17const numberBox = new Box<number>(100);
18console.log(numberBox.getValue());  // 100
19
20const stringBox = new Box<string>("Hello");
21console.log(stringBox.getValue());  // Hello
  • Box<T> として、クラスの中で使われる型 T をジェネリクスとして宣言しています。これにより、異なる型に対しても同じクラスを再利用できます。

ジェネリックインターフェース

インターフェースにもジェネリクスを使用できます。

 1interface Pair<T, U> {
 2    first: T;
 3    second: U;
 4}
 5
 6const numberStringPair: Pair<number, string> = { first: 1, second: "One" };
 7console.log(numberStringPair);  // { first: 1, second: 'One' }
 8
 9const booleanArrayPair: Pair<boolean, number[]> = { first: true, second: [1, 2, 3] };
10console.log(booleanArrayPair);  // { first: true, second: [ 1, 2, 3 ] }
  • Pair<T, U> で2つのジェネリクス型を指定し、異なる型の組み合わせを持つオブジェクトを定義できます。

デフォルト型引数

ジェネリクスの型引数には、デフォルトの型を指定することも可能です。

1function createArray<T = string>(length: number, value: T): T[] {
2    return Array(length).fill(value);
3}
4
5console.log(createArray(3, "a"));   // ['a', 'a', 'a']
6console.log(createArray(3, 100));   // [100, 100, 100]
  • <T = string> としてデフォルトの型引数を string に設定しています。明示的に型を指定しない場合、Tstring 型になります。

ジェネリック型エイリアス

ジェネリクスを型エイリアス(type)としても使用できます。

 1type Result<T> = {
 2    success: boolean;
 3    data: T;
 4};
 5
 6const successResult: Result<number> = { success: true, data: 42 };
 7const errorResult: Result<string> = { success: false, data: "Error occurred" };
 8
 9console.log(successResult);  // { success: true, data: 42 }
10console.log(errorResult);    // { success: false, data: 'Error occurred' }
  • Result<T> は、T 型のデータを含む結果オブジェクトを表します。このようにジェネリクスを使って柔軟な型エイリアスを作ることができます。

複数のジェネリック型

複数のジェネリック型を使用して、さらに汎用性の高い関数やクラスを定義することも可能です。

1function merge<T, U>(obj1: T, obj2: U): T & U {
2    return { ...obj1, ...obj2 };
3}
4
5const person = { name: "Alice" };
6const job = { title: "Engineer" };
7
8const merged = merge(person, job);
9console.log(merged);  // { name: 'Alice', title: 'Engineer' }
  • merge 関数では、2つの異なる型 TU を受け取り、それらを組み合わせて新しいオブジェクトを返します。

まとめ

  • ジェネリクスは、型をパラメータとして扱うことで、再利用可能で型安全なコードを実現します。
  • 関数、クラス、インターフェースでジェネリクスを使うことで、様々な型に対応する柔軟なロジックを記述できます。
  • 型引数に制約を加えたり、デフォルト型引数を設定したりすることで、ジェネリクスの使用範囲を制御できます。

ジェネリクスを使うことで、型に依存しない汎用的なコードを記述でき、TypeScriptの強力な型システムを最大限に活用できます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video