目次

TypeScriptの型システムは「バグを実行前に見つける」道具として広く使われるようになったが、Union型・Generic・型ガードを使いこなしている開発者はまだ少ない。本記事では型システムの核心を理解し、AIコード生成ツールとの相性も解説する。

なぜ型が重要か

// ❌ JavaScriptでの実行時エラー
function greet(user) {
    return `Hello, ${user.name.toUpperCase()}!`;
}

greet(null);  // TypeError: Cannot read property 'name' of null
// → 本番環境でクラッシュ

// ✅ TypeScriptで事前に検出
function greet(user: { name: string }): string {
    return `Hello, ${user.name.toUpperCase()}!`;
}

greet(null);  // コンパイルエラー: 'null' は '{ name: string }' に割り当てられません

型は「コードのドキュメント」であり「コンパイル時のテスト」でもある。

基本型

// プリミティブ型
const name: string = "Alice";
const age: number = 30;
const isActive: boolean = true;
const nothing: null = null;
const maybe: undefined = undefined;

// 配列
const scores: number[] = [1, 2, 3];
const tags: Array<string> = ["TypeScript", "AI"];

// オブジェクト型
type User = {
    id: number;
    name: string;
    email?: string;  // ? は省略可能(optional)
};

// 関数型
type Formatter = (value: number) => string;
const format: Formatter = (n) => `${n.toFixed(2)}円`;

Union型——複数の型を許容する

// Union型: AまたはBの型
type StringOrNumber = string | number;

function formatId(id: StringOrNumber): string {
    if (typeof id === 'string') {
        return id.toUpperCase();
    }
    return id.toString().padStart(6, '0');
}

// Literal Union: 特定の値のみ許容
type Status = 'pending' | 'active' | 'inactive' | 'deleted';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

function setStatus(status: Status) {
    // status は 4つの文字列のいずれかであることが保証される
}

// Discriminated Union: タグ付き共用体(パターンマッチに最適)
type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'rectangle'; width: number; height: number }
    | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        case 'triangle':
            return (shape.base * shape.height) / 2;
        // TypeScriptが全ケースを網羅しているか確認する
    }
}

Intersection型——型を合成する

type Timestamps = {
    createdAt: Date;
    updatedAt: Date;
};

type Identifiable = {
    id: string;
};

// & で型を合成
type Entity = Identifiable & Timestamps;

type User = Entity & {
    name: string;
    email: string;
};

// User は id, createdAt, updatedAt, name, email をすべて持つ
const user: User = {
    id: "u_123",
    name: "田中",
    email: "tanaka@example.com",
    createdAt: new Date(),
    updatedAt: new Date(),
};

Generic——型を引数にする

Genericは「型のテンプレート」だ。型を固定せずに、使う時に決める。

// ❌ any を使った非型安全な実装
function firstElement(arr: any[]): any {
    return arr[0];  // 返り値の型が不明
}

// ✅ Generic で型安全に
function firstElement<T>(arr: T[]): T | undefined {
    return arr[0];
}

const first = firstElement([1, 2, 3]);  // 型: number | undefined
const name = firstElement(['Alice', 'Bob']);  // 型: string | undefined

// 制約付き Generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: "Alice", age: 30 };
const userName = getProperty(user, 'name');  // 型: string
// getProperty(user, 'invalid');  // コンパイルエラー

// Generic インターフェース
interface Repository<T> {
    findById(id: string): Promise<T | null>;
    findAll(): Promise<T[]>;
    save(entity: T): Promise<T>;
    delete(id: string): Promise<void>;
}

interface User {
    id: string;
    name: string;
}

// 実装
class UserRepository implements Repository<User> {
    async findById(id: string): Promise<User | null> {
        // ...
    }
    // ...
}

型ガード——実行時の型絞り込み

TypeScriptの型はコンパイル時にしか存在しない。実行時に型を確認する仕組みが型ガードだ。

// typeof 型ガード
function processInput(input: string | number) {
    if (typeof input === 'string') {
        // ここでは input: string として扱える
        return input.toUpperCase();
    }
    // ここでは input: number として扱える
    return input * 2;
}

// instanceof 型ガード
class Dog { bark() { return 'Woof!'; } }
class Cat { meow() { return 'Meow!'; } }

function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        return animal.bark();  // Dog として扱える
    }
    return animal.meow();  // Cat として扱える
}

// カスタム型ガード(is キーワード)
interface Admin {
    role: 'admin';
    permissions: string[];
}

interface NormalUser {
    role: 'user';
    name: string;
}

type AnyUser = Admin | NormalUser;

// 型述語(Type Predicate)
function isAdmin(user: AnyUser): user is Admin {
    return user.role === 'admin';
}

function handleUser(user: AnyUser) {
    if (isAdmin(user)) {
        // user: Admin として扱える
        console.log(user.permissions);
    } else {
        // user: NormalUser として扱える
        console.log(user.name);
    }
}

// APIレスポンスの型ガード
function isApiError(response: unknown): response is { error: string; code: number } {
    return (
        typeof response === 'object' &&
        response !== null &&
        'error' in response &&
        'code' in response &&
        typeof (response as any).error === 'string' &&
        typeof (response as any).code === 'number'
    );
}

ユーティリティ型——型変換ツール

TypeScript組み込みの型変換ユーティリティが豊富に用意されている。

interface User {
    id: string;
    name: string;
    email: string;
    password: string;
}

// Partial: すべてのプロパティをoptionalに
type PartialUser = Partial<User>;
// = { id?: string; name?: string; email?: string; password?: string; }

// Required: すべてをrequiredに
type RequiredUser = Required<PartialUser>;

// Pick: 特定のプロパティを選択
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// = { id: string; name: string; email: string; }

// Omit: 特定のプロパティを除外
type UserWithoutPassword = Omit<User, 'password'>;

// Readonly: すべてを読み取り専用に
type ImmutableUser = Readonly<User>;

// Record: キーと値の型を指定したオブジェクト
type UserMap = Record<string, User>;
// = { [key: string]: User }

// ReturnType: 関数の戻り値の型を取得
async function fetchUser(id: string): Promise<User> { /* ... */ }
type FetchUserReturn = Awaited<ReturnType<typeof fetchUser>>;
// = User

// Parameters: 関数の引数の型を取得
type FetchParams = Parameters<typeof fetchUser>;
// = [id: string]

TypeScriptとAIコード生成

TypeScriptの型システムはAIコード生成と非常に相性が良い。

型情報がプロンプトになる

型定義が明確なコードベースでは、AIがより正確なコードを生成できる。

// 明確な型定義があれば、AIは正確な実装を生成できる
interface CreateUserRequest {
    name: string;
    email: string;
    role: 'admin' | 'user' | 'viewer';
    metadata?: Record<string, unknown>;
}

interface CreateUserResponse {
    user: User;
    token: string;
}

// AIへの指示: "上記の型定義を使ってユーザー作成APIを実装してください"
// → AIは型に合った実装を生成しやすい

Zodで実行時型検証

TypeScriptの型はコンパイル時のみ。外部データ(APIレスポンス・フォーム入力)にはZodでランタイム検証を追加する。

import { z } from 'zod';

// スキーマ定義(実行時検証 + TypeScript型推論)
const UserSchema = z.object({
    id: z.string().uuid(),
    name: z.string().min(1).max(100),
    email: z.string().email(),
    role: z.enum(['admin', 'user', 'viewer']),
    createdAt: z.coerce.date(),
});

// TypeScript型を自動生成
type User = z.infer<typeof UserSchema>;

// 実行時にAPIレスポンスを検証
async function fetchUser(id: string): Promise<User> {
    const response = await fetch(`/api/users/${id}`);
    const data = await response.json();

    // 型安全な検証(失敗したらエラーをスロー)
    return UserSchema.parse(data);
}

まとめ

TypeScriptの型システムを使いこなすと、バグを実行前に検出し、コードの意図を明確に表現できる。

  • Union型: AまたはBの型を表現。Discriminated Unionでパターンマッチを型安全に
  • Intersection型: 複数の型を合成(&
  • Generic: 型をパラメータ化して再利用可能な型を作成
  • 型ガード: 実行時に型を絞り込む(typeof・instanceof・カスタム型述語)
  • ユーティリティ型: Partial・Pick・Omit・Readonly など組み込みの型変換ツール
  • AIとの相性: 明確な型定義はAIコード生成の精度を上げる文脈になる
  • Zod: ランタイムの型検証とTypeScript型推論を一元管理

型システムは「制約」ではなく「コンパイラとAIの両方があなたを助けるためのガイドレール」と捉えると、より積極的に活用できるようになる。

免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。