【実務・中級編】Type Aliasで実現する「型レベルのバリデーション」:Zodとの比較 – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの型は「幻影」である:型レベルバリデーションの限界とZodによる現実解

TypeScriptの型システムは強力だが、多くのエンジニアが犯す最大の過ちがある。それは「コンパイル時の型定義を、実行時の信頼できるソース(Single Source of Truth)だと錯覚すること」だ。

TypeScriptの型は、コンパイル後にすべて消え去る。ランタイムでAPIから降ってくるJSONは、型定義とは無関係に振る舞う。この「コンパイル時とランタイムの断絶」をどう埋めるか。今回は、型レベルのテクニックとZodを組み合わせた、プロダクションレベルの堅牢な設計を伝授する。

—

1. なぜ「型だけのバリデーション」は破綻するのか

多くの現場で、`interface`や`type`でAPIのレスポンスを定義して満足しているコードを見る。

// 危険なコードの典型
interface User {
id: string;
email: string;
}

// 実際、APIが { id: 123 } を返してきたら?
// TypeScriptは文句を言わないが、ランタイムで “id.toUpperCase()” を呼べばクラッシュする。

TypeScriptは「静的解析ツール」に過ぎない。JSONを型アサーション(`as User`)した瞬間、その型定義は嘘の証明書になる。これがバグの温床だ。

—

2. 型レベルバリデーションの「限界」と「役割」

型レベルだけでバリデーション(`Template Literal Types`や`Conditional Types`など)を行う手法はある。確かに美しいが、それは「開発者のミスを防ぐ」ためであり、「外部からの不正なデータを防ぐ」ためではない。

型レベルの制約は、ドメインロジックの補完には有用だ。

// 型レベルで「メールアドレスの形式」を強制する(あくまで静的解析用)
type Email = `${string}@${string}.${string}`;

interface Config {
adminEmail: Email;
}

これはコンパイル時に「あ、この文字列はメールじゃないな」と気づかせるには最適だが、APIレスポンスの検証には無力だ。ここを混同してはいけない。

—

3. Zodによる「型」と「ランタイムバリデーション」の同期

実務における最適解は、「Zodスキーマを単一の真実(Source of Truth)とし、そこからTypeScriptの型を推論する」ことだ。これにより、型定義とバリデーションロジックの乖離を物理的に不可能にする。

実務で使える堅牢なパターン

import { z } from ‘zod’;

// 1. スキーマを定義(これがランタイムのバリデーター兼、型のソース)
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum([‘admin’, ‘user’]),
});

// 2. ここから型を抽出する(手書きの interface は捨てる)
type User = z.infer;

// 3. API連携時のバリデーション
async function fetchUser(userId: string): Promise {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();

// parseメソッドでランタイムチェック。失敗すれば例外を投げる
// これにより、以降のコードでは ‘data’ は安全な ‘User’ 型として扱える
return UserSchema.parse(data);
}

—

4. パフォーマンスと保守性のための設計指針

① z.infer を活用し、手書き型を追放せよ

`interface`を書いて、それに対応するZodスキーマを書く……そんな二重管理は今すぐやめるべきだ。型定義が更新された瞬間にバリデーションが追従しないコードは、レガシーコードへの第一歩である。

② バリデーションは「境界」で行う

APIレスポンス、フォーム入力、DBからの読み込み。これらの「外部とシステムが触れ合う境界」でのみZodでバリデーションを行え。内部ロジックでいちいち`parse`を呼ぶのはパフォーマンスの無駄だ。

③ パフォーマンスの懸念について

Zodのバリデーションコストを気にする者がいるが、JSONのパースコストやネットワークレイテンシと比較すれば、Zodの検証コストは誤差の範囲だ。それよりも「バリデーション漏れによるデータ不整合のデバッグコスト」の方が圧倒的に高い。迷わず使え。

—

結論:型は「守り」、バリデーションは「攻め」

TypeScriptの型システムは、コードの意図を明示し、開発時の生産性を高めるための「守り」のツールだ。対してZodは、信頼できない外部世界と戦うための「攻め」の武器である。

  • `interface`/`type`: 内部のデータ構造を整理するための設計図。
  • Zod: 外部から流れてくる「毒」を浄化し、型システムに安全に流し込むためのフィルター。

この二つを使い分けることこそが、TypeScriptを掌握するということだ。明日からのコードレビューで、`as`(型アサーション)を多用しているコードを見つけたら、即座にリファクタリングの対象とすることをお勧めする。

「TypeScriptの型は、実行時には存在しない」。この事実を常に胸に刻んでコードを書け。

タイトルとURLをコピーしました