はじめに
TypeScriptの勉強をしている中で、ジェネリクスといった概念について新しい学びが多かったので、本記事ではジェネリクスの概念について、図解を用いて自分なりにわかりやすく記事をまとめてみようと思います。
登場背景
ジェネリクスの登場背景は、下図のように「型の安全性」「コードの共通化」の両方を担保できる機能が欲しかったことにあります。

それに対して、従来は、その一方のみを担保できる機能しかありませんでした。
まずは、それらの機能について紹介します。
①型の安全性のみ担保
ここでは、型の安全性について説明して行きます。型の安全性とは「型が具体的に設定されているか(string, numberなど...)」ということです。型が具体的に設定されていれば、型に関するエラーが発生した場合にコンパイルしたタイミングで検出することができるため、バグを減らすことが可能となります。
下記ソースコードでは、型の安全性のみを重視して書いたコードを示しています。これらを見ると、意図した型を設定できているのに対して、似たようなコードが複数存在することで、コードの共通化が行えておらず保守性が下がってしまうといった問題があります。
function chooseRandomlyString(v1: string, v2: string): string {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomlyString("勝ち", "負け")) // 勝ち or 負け
function chooseRandomlyNumber(v1: number, v2: number): number {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomlyNumber(1, 0)) // 1 or 0
function chooseRandomlyBoolean(v1: boolean, v2: boolean): boolean {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomlyBoolean(true, false)) // true or false
他にも、functionをまとめて書くこともできるので、ご紹介します。
function chooseRandomly(v1: number, v2: number): number;
function chooseRandomly(v1: string, v2: string): string;
function chooseRandomly(v1: boolean, v2: boolean): boolean;
function chooseRandomly(v1: any, v2: any): any {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomly("勝ち", "負け")) // 勝ち or 負け
console.log(chooseRandomly(1, 0)) // 1 or 0
console.log(chooseRandomly(true, false)) // true or false
※上記構文は、この記事で紹介されている構文を参考に作成しました。
②コードの共通化のみ担保
それでは、コードの共通化について重視するとどのようなコードになるのでしょうか?
コードの共通化とは、「共通化できる処理を関数でまとめられているか」ということです。コードの共通化が行えていれば、該当する箇所を修正する場合、共通化したメソッドの中身のみを修正すれば良いので、修正箇所が最小限で済むメリットがあり、保守性の向上が見込めます。
下記ソースコードでは、コードの共通化のみを重視して書いたコードを示しています。これらを見ると、コードの共通化は行えているのに対して、型をany型(どの型でも良い)で定義しなければならず、型エラーによるバグを見つけられなくなってしまうといった問題があります。
function chooseRandomly(v1: any, v2: any): any {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomly(1, 0)) // 1 or 0
③ジェネリクス
冒頭で示したマトリクスを見ると、従来の構文では、型の安全性とコードの共通化どちらかしか担保できないといった問題がありました。そこで登場したのが、ジェネリクスといった概念になります。
詳しくは、次章の「ジェネリクスの構文」で説明をしますが、これを用いることで、型の安全性を担保しつつコードの共通化を行えるといった利点があります。
ジェネリクスの構文
基本形
まず、ジェネリクスの基本構文は下記のようになります。ポイントは、型宣言の部分をT
で一般化しているところになります。これにより、型の安全性を担保しつつ、コードの共通化を行うことが可能になります。
function generics<T>(value: T): T {
return value;
}
console.log(generics<string>("テスト")); // テスト
console.log(generics<number>(0)); // 0
console.log(generics<boolean>(true)); // true
利用例
上記で説明したソースコードに対して、ジェネリクスを適用すると下記のようになります。
function chooseRandomly<T>(v1: T, v2: T): T {
return Math.random() >= 0.5 ? v1 : v2;
}
console.log(chooseRandomly<string>("勝ち", "負け")) // 勝ち or 負け
console.log(chooseRandomly<number>(1, 0)) // 1 or 0
console.log(chooseRandomly<boolean>(true, false)) // true or false
まとめ
今回は、ジェネリクスの登場背景とその構文について解説しました。
ジェネリクスは 「型の安全性(コンパイル時にバグを検出できる)」と「コードの共通化(保守性の向上が見込める)」 を賄えることが確認できました。
自分も実際に開発するときも、使いこなせるようにして行きたいですね。