1中身だけ違う入れ物 Box<T>
型定義に型引数を持たせる最も素朴な形です。Box<number> と Box<string> は1つの定義から作られた別の型で、value の型だけが入れ替わることを確かめます。
// 中身の型 T だけ、使うときに決められる入れ物
type Box<T> = {
value: T;
};
const countBox: Box<number> = { value: 3 };
const messageBox: Box<string> = { value: "hello" };
console.log(countBox.value + 1);
console.log(messageBox.value.toUpperCase());
// const wrongBox: Box<number> = { value: "abc" }; // T が number なのでエラー
2interface にも型引数を持たせる
型引数は type だけでなく interface でも使えます。固定のプロパティ(label)と型引数のプロパティ(value)を混ぜられること、ジェネリック関数の引数の型としても書けることを示します。
// interface にも型引数を持たせられる
interface Labeled<T> {
label: string;
value: T;
}
const temperature: Labeled<number> = { label: "気温", value: 23.5 };
const weather: Labeled<string> = { label: "天気", value: "晴れ" };
function show<T>(item: Labeled<T>): void {
console.log(item.label + ": " + item.value);
}
show(temperature);
show(weather);
3一覧ページの共通構造 Paged<T>
「items の中身だけ違い、外側の構造は同じ」という API レスポンスの典型をジェネリックな型でまとめた例です。ユーザー一覧でも商品一覧でも Paged<T> の1定義を使い回せます。
// 一覧と総件数をまとめる「ページ」の共通構造
type Paged<T> = {
items: T[];
total: number;
};
const userPage: Paged<{ userName: string }> = {
items: [{ userName: "Taro" }, { userName: "Hanako" }],
total: 50,
};
console.log(userPage.items[0].userName);
console.log("全" + userPage.total + "件中 " + userPage.items.length + "件を表示");
4成功か失敗かを表す Result<T>
ジェネリックな型と判別可能ユニオンの組み合わせで、「成功なら T 型の値、失敗ならメッセージ」を1つの型で表します。例外を投げずに失敗を返す実務のエラーハンドリングで多用される形です。
// 成功と失敗を1つのジェネリック型で表す
type Result<T> =
| { ok: true; value: T }
| { ok: false; message: string };
function parseAge(input: string): Result<number> {
const parsed = Number(input);
if (Number.isNaN(parsed)) {
return { ok: false, message: "数値ではありません: " + input };
}
return { ok: true, value: parsed };
}
const parsedOk = parseAge("28");
if (parsedOk.ok) {
console.log(parsedOk.value + 1); // 成功側に絞り込むと value が使える
}
console.log(parseAge("abc"));
5ジェネリック型を返すジェネリック関数
ジェネリックな型 CacheEntry<T> と、それを組み立てて返すジェネリック関数の連携です。保存した値の型が入れ物を経由しても失われない、キャッシュや履歴管理など実務のラッパー処理の基本形です。
// ジェネリックな型と、それを返すジェネリック関数の組み合わせ
type CacheEntry<T> = {
value: T;
savedAt: number;
};
function saveToCache<T>(value: T): CacheEntry<T> {
return { value: value, savedAt: Date.now() };
}
const savedUser = saveToCache({ userName: "Taro", age: 28 });
console.log(savedUser.value.userName); // T が推論され、中身に安全に触れる
const savedTags = saveToCache(["ts", "generics"]);
console.log(savedTags.value.length);
console.log(savedTags.savedAt > 0);