高度な型の使い方(2025年版)✨
TypeScriptの基本的な型を理解したら、次は高度な型を活用して、より複雑で型安全なコードを記述する方法を学びましょう。本記事では、ジェネリックス、ユニオン型とインターセクション型、そして複雑な型パターンの処理について最新情報を交えながら解説します。
1. ジェネリックス 🔄
概要
ジェネリックスは、型パラメーターを使用して関数やクラスを汎用化する仕組みです。これにより、異なるデータ型に対応しつつ型安全性を保つことができます。
基本例
以下はジェネリック関数の例です:
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // 型推論でTはnumber
const str = identity<string>("hello"); // 型推論でTはstring
💡 ポイント: <T> は「型のプレースホルダー」として機能します。呼び出し時に具体的な型が決定されます。
ジェネリック制約
ジェネリックスに制約を加えることで、特定のプロパティやメソッドを持つ型のみ許可できます。
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プロパティがない
応用例
ジェネリッククラスやインターフェースも作成できます:
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. ユニオン型とインターセクション型 🔗
ユニオン型
ユニオン型は、複数の型のいずれかを受け入れることができます。|(パイプ)記号で表します。
例:ユニオン型
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 演算子)を使用して安全に処理を分岐できます。
インターセクション型
インターセクション型は、複数の型を結合し、それらすべてのプロパティを持つ新しい型を作ります。&(アンパサンド)記号で表します。
例:インターセクション型
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によるパターンマッチング
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!
💡 ポイント: パターンマッチングでは、すべてのケースが網羅されているかチェックされるため、安全性が向上します。
条件付き型
条件付き型は、ある条件に基づいて異なる型を返す強力な機能です。
例:条件付き型
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
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の高度な型機能を活用することで、以下が実現できます:
- ジェネリックスによる再利用性と柔軟性の向上。
- ユニオン型とインターセクション型による複雑なデータモデルの安全な表現。
- パターンマッチングや条件付き型で効率的かつ安全なロジック構築。
これらの技術を駆使して、安全性と効率性に優れたコードを書いていきましょう! 🎉