TypeScript の高度なジェネリックを例を使って解説

TypeScript のジェネリックは、さまざまなデータ型を扱うことで、再利用可能で柔軟なコード コンポーネントを作成する方法を提供します。高度なジェネリックは、制約、既定値、複数の型などの追加機能を導入することでこの概念をさらに進め、開発者がより堅牢で型安全なコードを記述できるようにします。この記事では、例を使用して、ジェネリックのこれらの高度な概念について説明します。

一般的な制約

制約は、ジェネリックが受け入れることができる型を制限します。これにより、ジェネリック関数またはクラスに渡される型が特定の基準を満たすことが保証されます。たとえば、制約を使用して、ジェネリック型に特定のプロパティまたはメソッドがあることを確認できます。

function getLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

const stringLength = getLength("TypeScript");
const arrayLength = getLength([1, 2, 3]);

この例では、<T extends { length: number }> 制約により、getLength に渡される引数に length プロパティがあることが保証されます。

複数のジェネリック

TypeScript では、同じ関数、クラス、またはインターフェースで複数のジェネリック型を使用できます。これは、値のペアや複数の型を含むその他のデータ構造を扱う場合に便利です。

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const stringNumberPair = pair("TypeScript", 2024);

この関数 pair は、2 つの異なるジェネリック型 TU を受け入れ、両方の型を含むタプルを返します。

デフォルトのジェネリック型

TypeScript のジェネリックには、デフォルトの型も設定できます。これは、特定の型が指定されていない場合にジェネリックにフォールバック型を持たせたい場合に役立ちます。

function identity<T = string>(value: T): T {
    return value;
}

const defaultString = identity("Hello");  // T is string
const customNumber = identity<number>(100);  // T is number

この例では、identity に型が渡されない場合、デフォルトで string になります。

インターフェースでのジェネリックの使用

ジェネリックは、型が固定されていない複雑な構造を定義するためにインターフェースで使用できます。これにより、データの管理方法に柔軟性が追加されます。

interface Container<T> {
    value: T;
}

const stringContainer: Container<string> = { value: "Hello" };
const numberContainer: Container<number> = { value: 42 };

Container インターフェイスは、任意の型の値を保持するように設計されており、特定の型を持つさまざまな種類のコンテナーを可能にします。

ジェネリッククラス

TypeScript のクラスもジェネリックにすることができます。これは、データ ストレージ クラスやコレクション クラスなど、さまざまなデータ型で動作するクラスを設計するときに特に便利です。

class DataStore<T> {
    private data: T[] = [];

    add(item: T): void {
        this.data.push(item);
    }

    getAll(): T[] {
        return this.data;
    }
}

const stringStore = new DataStore<string>();
stringStore.add("Hello");
stringStore.add("TypeScript");

const numberStore = new DataStore<number>();
numberStore.add(42);

この例では、DataStore クラスは任意のタイプのデータで動作し、要素を保存および取得するためのタイプセーフな方法を提供します。

結論

TypeScript の高度なジェネリックは、柔軟で再利用可能、かつ型安全なコードを書くための強力なツールです。クラスやインターフェースで制約、複数の型、デフォルト値、ジェネリックを使用することで、開発者はより複雑で堅牢なコードを書くことができます。これらの高度な概念を理解して活用することで、柔軟性が高まり、アプリケーション全体で型の安全性が確保されます。