TypeScriptには初見殺しの文法がたくさんあり、Googleで検索してもさらにわからない文法が出てきたりして諦めてしまう初心者の方が多くいらっしゃいます。 この記事ではどのサイトよりもわかりやすく、TypeScriptの少し難しめの文法について解説していきたいと思います。
ジェネリクス
この回ではTypeScriptのジェネリクスについて学んでいきましょう。
<T>
このような文法をTypeScriptで見たことはないでしょうか?
これをジェネリクスといい、ジェネリクスでは型を<>
で囲みます。
ジェネリクスは型の引数だと覚えると理解が深まります。型を代入して新たな型を得るイメージです。 型が決まっていない関数やクラスなどをanyの代わりにTで表現し後で利用用途に合わせてTに具体的な型を代入して使ってもらう場合などに利用できます。
関数での利用
例えば下のコードを見ていきましょう。
type HasId = {
id: string;
};
function find<T extends HasId>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id);
}
const goods = [
{ id: "1", price: 1000, name: "goods-a" },
{ id: "2", price: 2000, name: "goods-b" },
{ id: "3", price: 3000, name: "goods-c" }
];
const currentItem = find(goods, "1");
今回の例では、find関数で使われているジェネリクスの<>
で囲われている部分はT extends HasId
となっています。
T extends HasId
はTの型はHasId型を拡張した何かの型という意味になります。
ですので、少なくともTの型にstring型のidがプロパティとして含まれている必要があります。
const currentItem = find(goods, "1");
この部分で、find関数の第一引数にgoodsが代入されていることによりTの型が決まります。
find関数の第一引数はT[]
型と定義されていて、goodsの項目は{id: string, price: number, name: string}
のため、
Tが、{id: string, price: number, name: string}
だと判明します。
よって返り値のT | undefined
と定義されているので、この時の返り値の型は{id: string, price: number, name: string} | undefined
となります。
後から代入される値や型によって最初にTとしておいた部分の型が決まっていくのがジェネリクスの特徴です。
クラスでの利用
またジェネリクスはクラスにも利用できます。 以下の例を見ていきましょう
type HasId = {
id: string;
};
class ItemManager<T extends HasId> {
items: T[]
constructor(items: T[]) {
this.items = items;
}
addNewItem(item: T) {
this.items.push(item)
}
findItem(id: string): T {
return items.find((item) => item.id === id);
}
}
const goods = [
{ id: "1", price: 1000, name: "goods-a" },
{ id: "2", price: 2000, name: "goods-b" },
{ id: "3", price: 3000, name: "goods-c" }
];
const itemManger = new ItemManager(goods)
今回の例でも、new ItemManager
の箇所で、goods
が代入されているので、T[]
型に対応するのが、goods
ということになります。
つまり、Tは{id: string, price: number, name: string}
型だと決まるため
このクラスのaddNewItemの引数やfindItemの帰り値が全て{id: string, price: number, name: string}
だと決まります。
また、以下のようにあらかじめジェネリクスに型を代入して使う方法もあります。
type Item = {
id: string;
price: number;
name: string;
}
const itemManger = new ItemManager<Item>([])
この場合もT=Item
だと決まるので、addNewItemの引数やindItemの帰り値もItem
になり同様の結果が得られます。
まとめ
ジェネリクスは型が決まっていない関数やクラスなどを定義でき、<T>
の部分に具体的な型を代入するとTと定義しておいた他の部分の型も決まっていくという構造が理解できましたでしょうか?
次のレッスンではジェネリクスの理解をより深めるために実践問題を進めていきましょう。