ジェネリクス
型を「あとから決める」柔軟な仕組み
ジェネリクスとは?
日常での例え:サイズ選択できるTシャツ
サイズ固定のTシャツ
「S用」「M用」「L用」を
それぞれ別に作る必要がある
サイズ選択可能
1つのデザインで「サイズはあとで選んでね」
→ どのサイズにも対応できる
なぜジェネリクスが必要?
問題:同じ処理を型ごとに書くのは無駄
- 数値の配列の最初の要素を返す関数
- 文字列の配列の最初の要素を返す関数
- ユーザーの配列の最初の要素を返す関数...
ジェネリクスなら:「どんな型でもOK」な関数を1つ作れる!
ジェネリクスなしの問題
anyを使う場合
型安全性が失われる
typescript
// anyを使うと何でも受け取れるが...
function getFirst(arr: any[]): any {
return arr[0];
}
const nums = getFirst([1, 2, 3]);
// numsの型はany... 数値かどうか分からない
const names = getFirst(["田中", "鈴木"]);
// namesの型もany... 文字列かどうか分からない
// 型の恩恵がなくなってしまう!プレビュー
⚠️ anyは型情報が消える
戻り値の型が分からない
型ごとに関数を作る
同じコードの重複
typescript
// 型ごとに関数を作ると...
function getFirstNumber(arr: number[]): number {
return arr[0];
}
function getFirstString(arr: string[]): string {
return arr[0];
}
function getFirstUser(arr: User[]): User {
return arr[0];
}
// 同じ処理なのに何度も書く... 非効率!プレビュー
⚠️ 同じコードの繰り返し
メンテナンスが大変
ジェネリクスの基本
ジェネリック関数
型を引数のように受け取る
typescript
// <T> が型パラメータ(型の引数)
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 使う時に型を指定
const num = getFirst<number>([1, 2, 3]);
// numの型はnumber
const name = getFirst<string>(["田中", "鈴木"]);
// nameの型はstring
// 型推論もできる(省略可能)
const item = getFirst([true, false]);
// itemの型はboolean(自動推論)プレビュー
function getFirst<T>(arr: T[]): T
// Tは「あとで決める型」
複数の型パラメータ
2つ以上の型も使える
typescript
// 2つの型パラメータ
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair<string, number>("age", 25);
// resultの型は [string, number]
// 型推論で省略
const auto = pair("name", "田中");
// autoの型は [string, string]
// キーと値のマップ
function toObject<K extends string, V>(key: K, value: V) {
return { [key]: value };
}プレビュー
<T, U>
// 複数の型パラメータも可能
ジェネリック制約(extends)
日常での例え:条件付きの募集
「18歳以上の方を募集」= 誰でもいいけど年齢条件あり
ジェネリクスも「〇〇を満たす型のみ」と制約をつけられます
extendsで制約をつける
特定の型のみ許可
typescript
// lengthプロパティを持つ型のみ許可
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length);
}
logLength("hello"); // ✅ OK (文字列はlengthあり)
logLength([1, 2, 3]); // ✅ OK (配列はlengthあり)
logLength({ length: 5 }); // ✅ OK
logLength(123); // ❌ エラー!numberにlengthはない
logLength({ name: "a" }); // ❌ エラー!lengthがないプレビュー
T extends { length: number }
// lengthを持つ型のみ許可
interfaceを使った制約
より明確な制約
typescript
interface HasId {
id: number;
}
// HasIdを満たす型のみ許可
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
interface User extends HasId {
name: string;
}
interface Product extends HasId {
title: string;
price: number;
}
const users: User[] = [{ id: 1, name: "田中" }];
const products: Product[] = [{ id: 1, title: "PC", price: 100000 }];
findById(users, 1); // ✅ User | undefined
findById(products, 1); // ✅ Product | undefinedプレビュー
// HasIdを満たす型なら
// どんな型でも使える
ジェネリッククラス
クラスでジェネリクス
データ構造の実装に便利
typescript
// スタック(後入れ先出し)のジェネリッククラス
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
// 数値のスタック
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.pop(); // 2
// 文字列のスタック
const stringStack = new Stack<string>();
stringStack.push("hello");プレビュー
class Stack<T>
// 型安全なデータ構造
ジェネリックインターフェース
インターフェースでジェネリクス
APIレスポンスなどに便利
typescript
// APIレスポンスの汎用型
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
interface User {
id: number;
name: string;
}
interface Product {
id: number;
title: string;
}
// 使い分け
type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
type UsersResponse = ApiResponse<User[]>;
// 関数の戻り値にも
async function fetchUser(id: number): Promise<ApiResponse<User>> {
// ...
}プレビュー
ApiResponse<User>
ApiResponse<Product>
// 同じ構造、違うデータ
組み込みユーティリティ型
便利なジェネリクス型が用意されている
TypeScriptには、よく使うジェネリクス型が最初から用意されています。
Partial<T>
全プロパティをオプショナルに
typescript
interface User {
id: number;
name: string;
email: string;
}
// Partial<User> = 全プロパティが省略可能
type PartialUser = Partial<User>;
// {
// id?: number;
// name?: string;
// email?: string;
// }
// 更新時に便利
function updateUser(id: number, updates: Partial<User>) {
// 一部のプロパティだけ更新
}
updateUser(1, { name: "新しい名前" }); // ✅ OKプレビュー
Partial<User>
// 全部オプショナルに
Required<T>
全プロパティを必須に
typescript
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
// Required<Config> = 全プロパティが必須
type RequiredConfig = Required<Config>;
// {
// host: string;
// port: number;
// debug: boolean;
// }
const config: RequiredConfig = {
host: "localhost",
port: 3000,
debug: true
// 全部必須!省略するとエラー
};プレビュー
Required<Config>
// 全部必須に
Pick<T, K> と Omit<T, K>
プロパティの選択・除外
typescript
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Pick: 指定したプロパティだけ取り出す
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }
// Omit: 指定したプロパティを除外
type PublicUser = Omit<User, "password">;
// { id: number; name: string; email: string; }
// APIレスポンスで便利
function getPublicUser(user: User): PublicUser {
const { password, ...publicUser } = user;
return publicUser;
}プレビュー
Pick<User, "id" | "name">
Omit<User, "password">
Record<K, V>
キーと値の型を指定したオブジェクト
typescript
// Record<キーの型, 値の型>
type Scores = Record<string, number>;
const scores: Scores = {
math: 85,
english: 90,
science: 78
};
// Union型をキーに
type Status = "pending" | "approved" | "rejected";
type StatusMessages = Record<Status, string>;
const messages: StatusMessages = {
pending: "審査中です",
approved: "承認されました",
rejected: "却下されました"
};プレビュー
Record<string, number>
// オブジェクトの型を簡潔に
ジェネリクスまとめ
ジェネリクスを使うと
- 型安全性を保ちつつ汎用的なコードが書ける
- 同じ処理の重複を避けられる
- anyより安全
- 型推論で省略もできる
よく使う場面
- 配列操作の関数
- APIレスポンスの型
- データ構造(Stack, Queueなど)
- Reactコンポーネント