【TypeScript】ジェネリック型

はじめに

TypeScriptのジェネリック型は、再利用性の高いコンポーネントや関数を作成するための強力な機能です。ジェネリック型を使うことで、さまざまな型に対して動作するコードを記述できます。具体的には、関数やクラス、インターフェースにおいて、型をパラメータとして受け取ることができます。

ジェネリック型を使うと以下のようなメリットがあります。

  • 再利用性: ジェネリック型を使うことで、同じコードを異なるデータ型で再利用できます。
  • 型安全性: 型を明示することで、コンパイル時に型チェックが行われ、不正な型の使用を防ぐことができます。
  • 柔軟性: ジェネリック型は、特定の型に依存しない柔軟なコードを書くのに役立ちます。

様々なジェネリック型

関数のジェネリック型

まず、ジェネリック型を使わない関数の例を見てみましょう。

function identity(arg: number): number {
  return arg;
}

この関数は、引数としてnumber型を受け取り、number型を返します。これをジェネリック型を使って記述すると、以下のようになります。

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

この例では、<T>がジェネリック型パラメータを表しています。この関数は、引数として任意の型Tを受け取り、その型をそのまま返します。

let output1 = identity<string>("Hello");
let output2 = identity<number>(42);

console.log(output1); // "Hello"
console.log(output2); // 42

上記の例では、identity<string>identity<number>で、それぞれstring型とnumber型の引数を渡しています。型パラメータTは、それぞれの呼び出しで具体的な型に置き換えられます。

クラスのジェネリック型

クラスでもジェネリック型を使うことができます。

class Box<T> {
  contents: T;

  constructor(value: T) {
    this.contents = value;
  }

  getContents(): T {
    return this.contents;
  }
}

const stringBox = new Box<string>("Hello");
console.log(stringBox.getContents()); // "Hello"

const numberBox = new Box<number>(42);
console.log(numberBox.getContents()); // 42

この例では、Boxクラスがジェネリック型Tを持ち、contentsプロパティやgetContentsメソッドで使用しています。

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

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

interface Pair<T, U> {
  first: T;
  second: U;
}

const pair: Pair<string, number> = {
  first: "apple",
  second: 2,
};

console.log(pair); // { first: "apple", second: 2 }

この例では、Pairインターフェースが2つのジェネリック型パラメータTUを持ち、それぞれfirstsecondプロパティの型として使用しています。

制約付きジェネリック型

ジェネリック型とextendsを組み合わせると、型パラメータに制約を設けることができます。これにより、ジェネリック型が特定の条件を満たす型に限定されるため、型安全性を高めたり、特定のプロパティやメソッドが存在する型に対してのみ操作を行うことができます。

extendsは、ジェネリック型の型パラメータに制約を加えるために使用されます。これにより、ジェネリック型がある型のサブセットや特定のインターフェースを満たす必要があることを示します。

interface Lengthwise {
  length: number;
}

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

logLength("Hello"); // 5
logLength([1, 2, 3]); // 3

// logLength(10); // エラー: 'number'型は'Lengthwise'に割り当てられない

この例では、T extends Lengthwiseによって、T型がLengthwiseインターフェースを満たす型である必要があります。stringnumber[]Lengthwiseを満たす型なので、問題なく動作しますが、number型はLengthwiseに適合しないためエラーになります。

型パラメータの命名規則

通常、ジェネリック型の文字は1文字の大文字であり、意味が直感的である必要はありません。T,U,Vなどが一般的です。

型パラメータの命名は、その型パラメータが何を表しているのかを示すようにすると、コードの可読性が向上します。例えば、キーを示す型パラメータはK、値を示す型パラメータはVを使用するのが一般的です。

複数の型パラメータが必要な場合、T,U,Vなどを使って、役割を示すことが多いです。命名に一貫性を持たせることが重要です。

一般的によく使用される型パラメータの文字

T

一般的な型パラメータとして最もよく使われる文字です。Tは「Type」の略で、特定の型を示します。

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

U

複数の型パラメータがある場合、Tに続く第二の型パラメータとしてよく使われます。Uは「Unknown」の略と考えられることもありますが、特定の意味はありません。

function combine<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

K

キーを示す型パラメータとして使用されることが多いです。特にオブジェクトのキーやマップのキーを扱う際に使われます。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

V

値を示す型パラメータとして使用されることが多いです。特にマップの値を扱う際に使われます。

function getValue<K, V>(map: Map<K, V>, key: K): V | undefined {
  return map.get(key);
}

E

エラーや例外を示す型パラメータとして使われることがあります。

function handleError<E>(error: E): void {
  console.error(error);
}

R

戻り値の型を示す型パラメータとしてよく使われます。Rは「Return」の略です。

function process<T, R>(input: T, processor: (value: T) => R): R {
  return processor(input);
}

P

プロパティやパラメータを示す型パラメータとして使われることがあります。

function pickProperty<T, P extends keyof T>(obj: T, property: P): T[P] {
  return obj[property];
}

まとめ

ジェネリック型をうまく活用することで、柔軟かつ型安全なコードを書くことができます。TypeScriptを使う際には、ぜひジェネリック型を活用してみてください。

関連記事

【React】React-ToastifyのカラーテーマとPCの外観モードを連動させる方法【TypeScript】
# はじめに Webアプリでユーザー情報の変更などを行った際、処理が正常に完了したことをユーザーに伝えるために、画面の端に一時的にメッセージを表示する機能のことを「トースト」といいます。 >トーストとは、主にデスクトップアプリケーションの機 [...]
2022年7月31日 21:24
【TypeScript】「代入式の左辺には、省略可能なプロパティ アクセスを指定できません。」という静的解析エラーの原因と回避方法
# はじめに 最近、Next.js + TypeScriptを使ってフロントエンドの開発をしています。TypeScriptに関して興味はあったものの、実際に開発を行うまでは「JavaScriptを静的型付けに拡張した言語」という程度の知識しかあり [...]
2022年7月30日 10:03