TypeScriptのジェネリクスとは?初心者向けに<T>の意味や使い方を分かりやすく解説

TypeScriptのジェネリクスとは?初心者向けにの意味や使い方を分かりやすく解説

 

TypeScriptのジェネリクスとは

TypeScriptのジェネリクスとは、型を後から指定できる仕組みです。同じ処理を複数の型で使い回せるため、コードの重複を減らしながら型安全性を保てます。

少年A
前回言ってたジェネリクスってやつ、結局なんなの?
キノ
最初はピンとこなかったけど、「型の引数」だと思うとしっくりきたよ

この記事では、TypeScript(Generics)のジェネリクスについて、基本の書き方からany型との違い、実務での使用例、よくあるエラーまで、JavaScript経験者向けに整理します。まずは基本をまとめた記事も参考にしてください。

スポンサーリンク

ジェネリクスが必要な理由

ジェネリクスの必要性を理解するために、あえてジェネリクスを使わない例から見てみます。

function identity(value: string): string {
  return value;
}

// number型を渡したい場合、同じような関数をもう一つ書く必要がある
function identityNumber(value: number): number {
  return value;
}

「値をそのまま返すだけ」の関数なのに、型ごとに同じ処理を何度も書くのは無駄です。JavaScriptなら型を気にせず1つの関数で済んでいたはずの処理が、TypeScriptだと型の数だけ関数が増えてしまう、というのが最初にジェネリクスの必要性を感じた瞬間でした。

スポンサーリンク

<T>とは?基本の書き方

この問題を解決するのがジェネリクスです。関数名の後ろに<T>という型変数(型引数)を追加します。

function identity(value: T): T {
  return value;
}

const a = identity("kino");   // string型として扱われる
const b = identity(123);      // number型として扱われる

Tという文字は「型のための引数」だと考えると理解しやすいです。関数の引数に値を渡すのと同じように、<T>には「型」を渡している、というイメージです。慣習としてTが使われますが、複数の型変数がある場合はT、Uのようにアルファベット順で増やしていくのが一般的です。

型推論とは?<T>を省略できる場面

ジェネリクスは、呼び出す側で毎回型を指定しなくても、TypeScriptが自動で型を推論してくれる場合があります。

const c = identity("kino"); // を省略しても、渡した値からstring型と推論される

型推論についての詳しい仕組みは、別記事で解説しているのでこちらもあわせてどうぞ。

any型との違い

ジェネリクスを学び始めると、「any型を使えば同じことができるのでは?」という疑問が必ず出てきます。実際に比較してみると、違いがはっきりします。

function identityAny(value: any): any {
  return value;
}

const x = identityAny("kino");
x.toFixed(); // stringにtoFixedは無いのに、コンパイルエラーにならない
項目anyジェネリクス(<T>)
型情報消える(何でも許可)保持される
入力補完効きにくい型に応じて効く
安全性低い(typoに気づけない)高い

anyとunknownの違いについては、こちらの記事でさらに詳しく解説しています。

ジェネリクスのメリット

  • 型ごとに同じ関数を何度も書かずに済む(コードの重複を減らせる)
  • anyと違い、型情報を保ったまま処理できる
  • 呼び出す型に応じてエディタの入力補完が効く

ジェネリクスのデメリット・注意点

便利な一方で、使いすぎには注意が必要だと感じています。

  • 型変数が増えるとコードが読みにくくなりやすい
  • 無条件にTを受け取ると、意図しない型まで通ってしまうことがある(extendsで制約を付けると防げます)
  • 初心者のうちは「とりあえずジェネリクス」にせず、必要になった箇所から使う方が理解しやすい

実務で使う場面:React・Promiseでの例

ジェネリクスというと構えてしまいますが、実はTypeScriptを書いていると意識せずに使っていることが多いです。

// Promise<T>:API通信の戻り値の型を指定する
async function fetchUser(id: string): Promise<{ name: string; age: number }> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// React:useStateでも<T>がよく使われる
const [user, setUser] = useState<{ name: string; age: number } | null>(null);

Promise<T>useState<T><>の部分も、実はジェネリクスの仕組みです。「PromiseやuseStateという入れ物に、どの型を入れるか」を指定していると考えると、身近な存在に感じられました。実際にReactでAPI連携の実装をする中で、ジェネリクスの意味を意識する場面が何度もありました。

よくあるエラー

function getId(param: T): number {
  return param.id;
  // Property 'id' does not exist on type 'T'.
}

これは、Tが「どんな型か分からない」状態のまま.idを使おうとして起きるエラーです。extendsを使って、Tが持つべき形をあらかじめ制約しておくと解決します。

function getId(param: T): number {
  return param.id; // OK
}

よくある質問

Q. ジェネリクスとは何ですか?
A. 型を後から指定できる仕組みで、同じ処理を複数の型で使い回すために使います。

Q. <T>のTって何ですか?
A. 「型のための引数(型変数)」です。慣習的にTという名前が使われますが、自由に名付けられます。

Q. any型との違いは何ですか?
A. anyは型情報が消えてしまいますが、ジェネリクスは型情報を保ったまま処理できるため、入力補完や型チェックが効きます。

Q. ジェネリクスはいつ使えばいいですか?
A. 「同じ処理を複数の型に対して行いたいとき」が目安です。配列操作、API通信のレスポンス型、Reactのstateなどでよく使われます。

まとめ

  • ジェネリクスは「型を後から指定できる仕組み」で、同じ処理を型ごとに書き直す無駄を防げる
  • <T>は型のための引数、というイメージで理解すると腹落ちしやすい
  • anyと違い型情報を保持できるため、実務ではAPI通信やReactのstateなどで頻繁に使われる

公式ドキュメントにも、今回紹介した内容を含むより詳しい解説があります。

TypeScript公式ドキュメント:Generics

もう少し実践的な使い方まで知りたい場合は、こちらの解説記事や動画も参考になります。

【TypeScript】ジェネリクスも便利だよ #初心者向け(Qiita)
Learn TypeScript With This Crash Course(freeCodeCamp)

少年A
思ったより特別なものじゃなかったんだね
キノ
うん。「型の使い回し」だと思うと、身構えなくてよくなった

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です