TypeScriptで学ぶ型安全な開発プラクティス【その３】高度な型の使い方

https://qiita.com/YushiYamamoto/items/1721e9030e1612125ceb

# 高度な型の使い方（2025年版）✨

TypeScriptの基本的な型を理解したら、次は高度な型を活用して、より複雑で型安全なコードを記述する方法を学びましょう。本記事では、**ジェネリックス**、**ユニオン型とインターセクション型**、そして**複雑な型パターンの処理**について最新情報を交えながら解説します。

---

## **1. ジェネリックス** 🔄

### **概要**
ジェネリックスは、型パラメーターを使用して関数やクラスを汎用化する仕組みです。これにより、異なるデータ型に対応しつつ型安全性を保つことができます。

### **基本例**
以下はジェネリック関数の例です：

```typescript
function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42); // 型推論でTはnumber
const str = identity<string>("hello"); // 型推論でTはstring
```

💡 *ポイント*: `<T>` は「型のプレースホルダー」として機能します。呼び出し時に具体的な型が決定されます。

### **ジェネリック制約**
ジェネリックスに制約を加えることで、特定のプロパティやメソッドを持つ型のみ許可できます。

```typescript
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(item: T): void {
  console.log(item.length);
}

logLength({ length: 10, name: "test" }); // OK
logLength(123); // エラー: numberにはlengthプロパティがない
```

### **応用例**
ジェネリッククラスやインターフェースも作成できます：

```typescript
class DataStore<T> {
  private data: T[] = [];

  add(item: T): void {
    this.data.push(item);
  }

  getAll(): T[] {
    return this.data;
  }
}

const store = new DataStore<string>();
store.add("TypeScript");
console.log(store.getAll()); // ["TypeScript"]
```

---

## **2. ユニオン型とインターセクション型** 🔗

### **ユニオン型**
ユニオン型は、複数の型のいずれかを受け入れることができます。`|`（パイプ）記号で表します。

#### **例：ユニオン型**
```typescript
type ID = string | number;

function printID(id: ID): void {
  if (typeof id === "string") {
    console.log(`文字列ID: ${id}`);
  } else {
    console.log(`数値ID: ${id}`);
  }
}

printID("abc123"); // OK
printID(123); // OK
```

💡 *ポイント*: 型ガード（`typeof` や `in` 演算子）を使用して安全に処理を分岐できます。

---

### **インターセクション型**
インターセクション型は、複数の型を結合し、それらすべてのプロパティを持つ新しい型を作ります。`&`（アンパサンド）記号で表します。

#### **例：インターセクション型**
```typescript
type User = { name: string };
type Admin = { permissions: string[] };

type AdminUser = User & Admin;

const admin: AdminUser = {
  name: "Alice",
  permissions: ["read", "write"],
};
```

💡 *ポイント*: インターセクション型は複雑なデータモデルを作成する際に役立ちます。

---

## **3. 複雑な型パターンの処理** 🧩

### **パターンマッチング**
TypeScriptでは、ライブラリ（例：`ts-pattern`）を使用してパターンマッチングによる条件分岐が可能です。これにより、複雑な条件でも簡潔で安全なコードが書けます。

#### **例：ts-patternによるパターンマッチング**
```typescript
import { match } from "ts-pattern";

type Result =
  | { type: "success"; data: string }
  | { type: "error"; message: string };

const result: Result = { type: "success", data: "Hello, World!" };

const message = match(result)
  .with({ type: "success" }, (res) => `データ: ${res.data}`)
  .with({ type: "error" }, (err) => `エラー: ${err.message}`)
  .exhaustive();

console.log(message); // データ: Hello, World!
```

💡 *ポイント*: パターンマッチングでは、すべてのケースが網羅されているかチェックされるため、安全性が向上します。

---

### **条件付き型**
条件付き型は、ある条件に基づいて異なる型を返す強力な機能です。

#### **例：条件付き型**
```typescript
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
```

💡 *ポイント*: 条件付き型は柔軟な型操作やユーティリティタイプの作成に役立ちます。

---

### **ユーティリティタイプ**
TypeScriptには多くの組み込みユーティリティタイプがあります。以下はその一部です：

- `Partial<T>`: プロパティをすべてオプショナルにする。
- `Readonly<T>`: プロパティを読み取り専用にする。
- `Pick<T, K>`: 特定のプロパティのみ選択。
- `Omit<T, K>`: 特定のプロパティのみ除外。

#### **例：Partial と Readonly**
```typescript
interface User {
  id: number;
  name: string;
}

const updateUser = (user: Partial<User>): void => {
  console.log(user);
};

const userData: Readonly<User> = { id: 1, name: "Alice" };
// userData.name = "Bob"; // エラー：読み取り専用プロパティ
```

---

## **まとめ**

TypeScriptの高度な型機能を活用することで、以下が実現できます：
1. ジェネリックスによる再利用性と柔軟性の向上。
2. ユニオン型とインターセクション型による複雑なデータモデルの安全な表現。
3. パターンマッチングや条件付き型で効率的かつ安全なロジック構築。

これらの技術を駆使して、安全性と効率性に優れたコードを書いていきましょう！ 🎉