Skip to content

ジェネリクス

公開日:November 16, 2024更新日:November 28, 2024
JavaScriptTypeScriptCoding📄

ジェネリクスは、型をパラメータ化することで、再利用性と型安全性を高めたコードを書くための強力な機能です。ジェネリクスを使うことで、様々な型に対応する柔軟な関数やクラスを作成することができます。このセクションでは、ジェネリクスの基本から関数やクラスでの利用、ジェネリック制約について説明します。

ジェネリクスの基本

ジェネリクスは、型をパラメータとして扱うことで、様々な型に対応する柔軟なコードを記述するために使用します。例えば、配列の要素の型を固定せずに扱いたい場合にジェネリクスが役立ちます。

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

let output1 = identity<string>("Hello, World!");  // T の型は string
let output2 = identity<number>(42);  // T の型は number

この例では、identity関数は型パラメータTを受け取り、その型に応じた引数を扱うことができます。呼び出し時に具体的な型を指定することで、どの型でも対応可能な汎用的な関数が作れます。

関数とクラスでのジェネリクスの利用

ジェネリクスは関数だけでなく、クラスやインターフェースでも使用できます。これにより、様々なデータ型を扱うことができる汎用的なクラスを作成することが可能です。

関数でのジェネリクス

以下の例では、ジェネリクスを使った関数getArrayを定義しています。この関数は配列を受け取り、同じ型の配列を返します。

typescript
function getArray<T>(items: T[]): T[] {
  return new Array().concat(items);
}

let numberArray = getArray<number>([1, 2, 3]);
let stringArray = getArray<string>(["a", "b", "c"]);

クラスでのジェネリクス

クラスにもジェネリクスを使うことで、様々な型に対応するデータ構造を作成することができます。例えば、スタックを実装するクラスを以下のように定義できます。

typescript
class GenericStack<T> {
  private stack: T[] = [];

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

  pop(): T | undefined {
    return this.stack.pop();
  }

  peek(): T | undefined {
    return this.stack[this.stack.length - 1];
  }
}

let numberStack = new GenericStack<number>();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop());  // 20

let stringStack = new GenericStack<string>();
stringStack.push("Hello");
stringStack.push("World");
console.log(stringStack.pop());  // World

上記の例では、GenericStackクラスがジェネリック型Tを使用しており、任意の型の要素を扱うことができます。これにより、number型やstring型のスタックを簡単に作成できます。

ジェネリック制約

ジェネリクスを使う際には、型に制約を設けて、ジェネリック型が持つべき特定のプロパティやメソッドを定義することができます。ジェネリック制約を使うことで、扱う型に対する条件を指定することが可能です。

制約を使ったジェネリクス

例えば、オブジェクトにlengthプロパティがあることを前提とする関数を作りたい場合、ジェネリック制約を使って型に条件を課すことができます。

typescript
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): void {
  console.log(arg.length);
}

logLength("Hello");  // OK, string 型には length プロパティがある
logLength([1, 2, 3]);  // OK, 配列も length プロパティを持つ
// logLength(42);      // エラー: number 型は length プロパティを持たない

この例では、インターフェースLengthwiseを使って、lengthプロパティを持つ型であることをTに要求しています。その結果、lengthプロパティがない型は渡せなくなります。

ジェネリック制約を使うことで、型パラメータに対して条件を追加し、より強力で安全なコードを書くことができます。

まとめ

ジェネリクスを使うことで、型を柔軟に扱いながらも型安全性を保つことができます。関数やクラスにジェネリクスを導入することで、様々な型に対応できる汎用的なコードを作成でき、ジェネリック制約を使うことで、特定の条件を満たす型のみに対応することが可能になります。これにより、再利用性の高い、保守しやすいコードを書くことができるでしょう。