さとまたプログラミングラボ

ジェネリクス

型を「あとから決める」柔軟な仕組み

ジェネリクスとは?

日常での例え:サイズ選択できる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コンポーネント