第14章:TSならでは① “型と実行時は別物”を理解する🧠⚠️
14.1 今日のゴール🎯✨
この章が終わったら、これができるようになるよ〜!😊💪
- 「型があるのに壊れる」理由を説明できる🗣️💡
- 外から来るデータ(API/JSON/Storage/フォーム)を安全に扱える🛡️📦
- unknown → 検証 → 変換 → ドメイン型の流れを自分で作れる🔁✨
14.2 まず結論:TypeScriptの“型”は実行時に消える👻🫥
TypeScriptは 実行するときはJavaScript になるよね? そのとき、型注釈は消える=実行時に守ってくれないの🥲
- 型注釈は消える(出力JSに残らない)🫥 (TypeScript)
as(型アサーション)も消えるから、間違ってても実行時に怒ってくれない🙃 (TypeScript)
なので…👇 **「型があるから安全」じゃなくて「型+実行時チェックがあるから安全」**だよ🛡️✨
14.3 “事故が起きる場所”はだいたい決まってる🚨📍

型が効かない危険ゾーンはここ!👀💦
- 🌐 APIレスポンス(
fetch().json()) - 🧾
JSON.parse()(ファイル読み込みも同じ) - 💾
localStorage/sessionStorage - 📝 フォーム入力(文字列で来がち)
- 🔗 URLクエリ(
?page=1みたいなやつ) - 🧩 外部SDK/プラグインが返すデータ
共通点はこれ👇 **「中身が本当にその形か、実行時に確かめないといけない」**📦🧠
14.4 安全ルートはこれだけ覚えればOK!🛣️✨

✅ 正しい流れ(黄金ルート)🥇
- 外部入力は unknown として受け取る📦
- スキーマ検証(または型ガード)でチェック🧪
- ドメイン型に変換(必要なら整形・正規化)🧼
- 以降の中心ロジックは “確かな型”だけで動かす🧠✨
この流れができると、「中心ロジックが軽い」「変更が怖くない」に直結するよ〜🎀💪
14.5 代表的なやり方 3つ🧰✨
① スキーマバリデーション(いちばん実務向き)🛡️✅
最近は Zod v4 が安定版で、機能追加も活発だよ📈✨ (Zod)
- 書いたスキーマからTS型を推論してくれる(重複しにくい)🧠
- 失敗時のエラーが取りやすい🧾
safeParseが超便利👍
Valibotも「TSの型は実行されないけど、スキーマは実行できる」って思想がはっきりしてて良いよ〜🧩✨ (GitHub)
② 型ガード(小さいチェックに強い)🕵️♀️✅
ちょっとした typeof / in / Array.isArray で守る方法✨
ただし 複雑なオブジェクトになると書くのが大変💦
③ “変換器”を必ず通す(Mapper)🧼🔁
検証済みデータでも、
- 日付文字列 →
Date - 通貨 →
Money - ID →
TodoIdみたいに「アプリ内で扱いやすい形」に変換するのが強い💪✨
14.6 ハンズオン🛠️:APIレスポンス(未知)→検証→ドメイン型へ🔁✨
ここからは Zod v4 でやってみよ〜😊🧪 題材は「ToDo一覧を取ってくる」感じでいくね✅📚
14.6.1 依存追加📦
npm i zod@latest
14.6.2 フォルダ構成(迷子防止)📁🧭
domain:アプリの中心の型・ルール💎infra:API通信など外側🌐mapper:外→内の変換係🧼usecase:やりたいこと(手順)🎬
14.6.3 domain:ドメイン型を作る💎✨
// src/domain/todo.ts
export type TodoId = string & { readonly __brand: "TodoId" };
export type Todo = {
id: TodoId;
title: string;
done: boolean;
// 例:APIは文字列で返すけど、アプリ内はDateに寄せたい
dueAt: Date | null;
};
// 入口を絞るのがコツ✨(変換はここからしか通さない)
export function toTodoId(value: string): TodoId {
return value as TodoId;
}
14.6.4 infra:APIから来る“生データ”は unknown 扱い📦⚠️
// src/infra/todoApi.ts
export async function fetchTodosJson(): Promise<unknown> {
const res = await fetch("https://example.com/api/todos");
// ここ大事! json() の戻りは “信用しない” 👻
const data: unknown = await res.json();
return data;
}
14.6.5 schema:Zodで“実行時に”形を検証する🧪🛡️
// src/infra/todoSchema.ts
import { z } from "zod";
export const TodoApiSchema = z.object({
id: z.string(),
title: z.string(),
done: z.boolean(),
// 文字列 or null を許可(API側の都合をそのまま受ける)
dueAt: z.string().datetime().nullable(),
});
export const TodosApiSchema = z.array(TodoApiSchema);
export type TodoApi = z.infer<typeof TodoApiSchema>;
ポイント🧠✨
- APIの形はAPIの形としてスキーマを作る(無理にドメインに合わせない)👍
datetime()みたいな実行時チェックが効くのが強い🛡️
14.6.6 mapper:検証済み→ドメイン型へ変換🧼💎
// src/mapper/todoMapper.ts
import type { TodoApi } from "../infra/todoSchema";
import { toTodoId, type Todo } from "../domain/todo";
export function toTodoDomain(api: TodoApi): Todo {
return {
id: toTodoId(api.id),
title: api.title.trim(),
done: api.done,
dueAt: api.dueAt ? new Date(api.dueAt) : null,
};
}
ここが“安心ポイント”😊✨
- APIが変でも この前に検証があるから変換が素直に書ける👍
trim()とか正規化もここでやると、中心がスッキリ🧹
14.6.7 usecase:unknown → 検証 → 変換 の一本化🎬✅
// src/usecase/getTodos.ts
import { fetchTodosJson } from "../infra/todoApi";
import { TodosApiSchema } from "../infra/todoSchema";
import { toTodoDomain } from "../mapper/todoMapper";
import type { Todo } from "../domain/todo";
export type GetTodosResult =
| { ok: true; todos: Todo[] }
| { ok: false; message: string };
export async function getTodos(): Promise<GetTodosResult> {
const raw = await fetchTodosJson();
// 失敗してもthrowしない方がUIで扱いやすいよ🙂✨
const parsed = TodosApiSchema.safeParse(raw);
if (!parsed.success) {
return {
ok: false,
message: "データ形式が想定と違ったよ🥲(APIレスポンスを確認してね)",
};
}
const todos = parsed.data.map(toTodoDomain);
return { ok: true, todos };
}
14.7 “やっちゃダメ集”🚫😱(超あるある)
❌ as で握りつぶす
const data = (await res.json()) as { id: string; title: string };
これ、実行時に何も確認してないから、壊れるときは盛大に壊れる💥
as は「変換」じゃなくて「黙らせ」だよ🙃 (TypeScript)
❌ any に逃げる
逃げた瞬間から、型の恩恵が消えてデバッグ地獄になりがち😵💫
❌ “中心ロジック”で検証を始める
中心が汚れていく…🍲💦 検証は **境界(入口)**でやるのがキレイ✨
14.8 AI活用プロンプト🤖✨(この章のやつ)
1) 壊れ方を想定して検証項目を列挙🧠🧾
「この入力データ、壊れ方(欠損/型違い)を想定して検証項目を列挙して」🤖
おすすめの追い質問💡
- 「“最悪の壊れ方TOP10”を想定して、Zodスキーマ案も出して」🛡️
- 「その検証が必要な理由も一言で」📝
14.9 ミニ理解チェック🧠✅(3問クイズ)
as Userを書いたら、実行時にUserチェックが走る?👀fetch().json()の戻りを “unknown” にするメリットは?📦- 検証(schema validation)はどこでやるのが自然?🚪
答え🎀
- 走らない(型は消える)👻 (TypeScript)
- 「信用しない」設計になって、境界で守れる🛡️
- アプリの入口・境界(infra→usecaseに渡す前)✅
14.10 今どきTSまわり小ネタ📰✨(知ってると嬉しい)
- TypeScript 5.9 がリリースされていて、次の 6.0 は 7.0(ネイティブ移行)への“橋渡し”という位置づけが語られてるよ🧱➡️🚀 (Microsoft for Developers)
- コンパイラや言語サービスのネイティブ化(プレビュー)も進んでるよ⚡ (Microsoft for Developers)
(でもこの章の結論は変わらない:型は実行時に守ってくれない!👻)
まとめ🎉✨
- 型は開発中の味方、でも 実行時の警備員ではない👮♀️❌
- 外部入力は unknown → 検証 → 変換 → ドメイン が最強ルート🛡️🔁
- スキーマ検証(Zod/Valibotなど)+Mapperで、中心ロジックがめちゃくちゃキレイになるよ〜💎✨ (Zod)