zukucode
主にWEB関連の情報を技術メモとして発信しています。

TypeScript ジェネリクスで引数と戻り値の型をつなぐ

同じ処理を文字列と数値の両方に使いたいとき、引数をanyにすると戻り値の型も分からなくなります。ジェネリクスを使うと「受け取った型と返す型が同じ」という関係を表せます。

最初の値を返す関数

function first<T>(items: readonly T[]): T | undefined {
  return items[0];
}

const name = first(['ノート', 'ペン']); // string | undefined
const price = first([100, 200]);      // number | undefined

Tは関数を呼ぶときに決まる型です。ここでは引数から推論されるため、通常はfirst<string>(...)と明示する必要はありません。空の配列も渡せるので、戻り値にはundefinedを含めます。

anyとの違い

function firstAny(items: any[]): any {
  return items[0];
}

const value = firstAny([100, 200]);
value.toUpperCase(); // コンパイルできるが実行時に失敗

first<T>なら数値の結果を文字列として扱う誤りを、コンパイル時に見つけられます。

型に必要な条件を付ける

何でも受け取れる関数では、Tのプロパティを直接読めません。IDを持つ値だけに限定するなら制約を付けます。

function findById<T extends { id: string }>(
  items: readonly T[],
  id: string,
): T | undefined {
  return items.find((item) => item.id === id);
}

const products = [{ id: 'p1', name: 'ノート', price: 200 }];
const product = findById(products, 'p1');
console.log(product?.price); // 200

戻り値は元の要素の型を保つため、priceも使えます。IDを持たない配列は渡せません。

文字列専用の関数なら、引数も戻り値もstringと書く方が簡単です。複数の値の間に型の関係を保ちたいときにジェネリクスを使います。


関連記事