メインコンテンツまでスキップ

第04章:TypeScriptの型は契約の入り口🟦🧩

この章でできるようになること🎯✨

  • 「型=契約」ってどういう意味?をスッキリ説明できる🤝
  • 型で守れること/守れないこと(実行時)を区別できる⚖️
  • “外から来るデータ”を安全に扱う入口(unknown+絞り込み)が書ける🚪🔒
  • 「オプショナル(?)の落とし穴」を回避できる(exactOptionalPropertyTypes)🕳️💡 (TypeScript)
  • satisfies で「型チェックだけして推論は保持」を使える🧠✨ (TypeScript)

0. まず超ざっくり:型は“約束の文章”📜🟦

TypeScriptの型は、**「この関数はこう呼んでね」「このデータはこういう形だよ」**っていう“約束”をコードに書くものだよ😊 そしてこの約束は、主に コンパイル時(型チェック時)に守られる んだけど、実行時(ランタイム)には型は存在しないのが大事ポイント⚠️

だからこそ「型は契約の入り口」=契約のスタート地点だけど、契約の全てではないよ🧩✨


1. 1分で体験:型が契約になる瞬間⚡🟦

// 呼び出す人(利用者)との約束=引数と戻り値の型✨
export function calcTotal(price: number, quantity: number): number {
return price * quantity;
}

// ✅ OK
calcTotal(120, 3);

// ❌ NG(契約違反)
// calcTotal("120", 3);

ここで calcTotal の型は、利用者にとっての 契約書 だよ📄✨ 「numberを2つ入れたらnumberが返る」って約束ね🤝


2. でも万能じゃない:型で守れない世界(実行時)🌪️😵‍💫

2-1. JSONは“型を無視して入ってくる”📦⚠️

外部(API/ファイル/DB/localStorageなど)から来るデータは、型がついてないことが多いよね。

// 例:どこかから来たJSON文字列
const json = `{"name":"Mina","age":"20"}`;

// parseした瞬間、実行時はただの “値”
const data = JSON.parse(json);

// ここで「ageはnumberのはず」と思い込むと…😇
console.log(data.age + 1); // "201" みたいな事故が起きる💥

型チェックはコンパイル時の世界だから、JSON.parse の結果が正しい形かどうかは保証してくれないんだ〜😵‍💫


3. 契約の入口としての“境界”を作ろう🚪🔒(any禁止、unknown推し✨)

Type Check at Boundary

3-1. any は契約を破壊する😱🧨

any を使うと、型チェックが止まって “契約が紙切れ” になるよ💔

function unsafe(x: any) {
// なんでも通る😇
return x.toUpperCase();
}

3-2. unknown は「まず疑う」から安全🕵️‍♀️✨

外部入力は unknown で受けて、中で確認してから使うのがコツ!

type User = {
name: string;
age: number;
};

function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) return false;

const v = value as Record<string, unknown>;
return (
typeof v.name === "string" &&
typeof v.age === "number"
);
}

function parseUser(json: string): User {
const value: unknown = JSON.parse(json);

if (!isUser(value)) {
throw new Error("Userの形じゃないよ🥺");
}

return value;
}

```mermaid
flowchart TD
Input[外部入力<br>API/JSONなど] -->|unknown| Gate{境界チェック}
Gate -->|isUser OK?| Safe[型安全な世界<br>User型として扱う]
Gate -->|NG| Error[エラーとして弾く<br>throw Error]

style Input fill:#f9f,stroke:#333,stroke-width:2px
style Safe fill:#ccf,stroke:#333,stroke-width:2px

これが 「契約の入口」 って感じ!🚪✨

  • 境界でチェック✅
  • 中に入れたら信頼して楽に書く🧡

4. “構造的部分型(Structural Typing)”という約束の形⚖️✨

Structural Typing (Duck Typing)

TypeScriptの型システムで一番おもしろくて大事なのが 「名前じゃなくて“形”が合っていればOK」 っていうルールだよ🦆

type Point2D = { x: number; y: number };

const p = { x: 1, y: 2, z: 999 };

// ✅ 形が合うからOKになりやすい(余計なzがあっても代入できる場面がある)
const a: Point2D = p;

便利だけど、契約設計では「混ざってほしくないものが混ざる」事故も起きる😵‍💫 次の「ブランド型」で対策するよ💪✨


5. “同じnumberだけど別物”を混ぜない🧷✨(ブランド型)

Brand Types

ユーザーIDと商品ID、両方 number だと混ざりやすいよね😇

type Brand<K, T> = K & { __brand: T };

type UserId = Brand<number, "UserId">;
type ProductId = Brand<number, "ProductId">;

function toUserId(n: number): UserId {
return n as UserId;
}

function toProductId(n: number): ProductId {
return n as ProductId;
}

function loadUser(id: UserId) {
// ...
}

const uid = toUserId(1);
const pid = toProductId(1);

loadUser(uid); // ✅
// loadUser(pid); // ❌ 混ぜる事故を防げる💖

「契約として区別したい型」は、こうやって守ると強いよ🛡️✨


6. オプショナル(?)は契約の地雷💣😵(absence と undefined は別!)

6-1. ありがちな誤解:「? は undefined もOKでしょ?」🤔

実は、設定で挙動が変わるよ⚙️

  • exactOptionalPropertyTypes: true にすると、「省略できる」と「undefinedを入れる」は別物として扱う(より契約っぽくなる)✨ (TypeScript)
// tsconfig: "exactOptionalPropertyTypes": true
type Profile = {
nickname?: string; // ない(省略)ならOK
};

const a: Profile = {}; // ✅ 省略OK
// const b: Profile = { nickname: undefined }; // ❌ “undefinedを入れた”は別扱い

6-2. 契約設計的に嬉しいこと💡

  • 「値がない」=プロパティが存在しない(欠けてる)
  • 「値がある」=ちゃんと値がある この区別ができると、JSON契約がブレにくいよ🧾✨

7. noUncheckedIndexedAccess で「取り出しは危険」を型に出す🧪⚠️

配列や辞書の [] って、存在しないかもだよね?

noUncheckedIndexedAccess: true を有効にすると、存在しない可能性(undefined)を型に出してくれるよ✅ (TypeScript)

// tsconfig: "noUncheckedIndexedAccess": true
const map: Record<string, number> = { apple: 100 };

const price = map["banana"];
// price は number | undefined になりやすい🍌⚠️

契約設計では「存在しないときの扱い」も契約の一部だから、これめっちゃ相性いい👍✨


8. satisfies:チェックだけして推論は残す🎀✨

「型に合ってるかチェックしたい。でも推論の便利さは失いたくない!」って時に神👼

type Route = {
path: `/${string}`;
method: "GET" | "POST";
};

const routes = [
{ path: "/users", method: "GET" },
{ path: "/orders", method: "POST" },
] satisfies Route[];
  • as Route[] みたいに雑にねじ込まない
  • “合ってるか”だけ検査してくれる
  • しかも、配列要素の具体的な推論は残りやすい✨ (TypeScript)

9. 章のテーマに直結:型で契約を育てるときの「安全な変更」🧬🌱

ここは次章以降で深掘りするけど、まず感覚だけ🎈

  • 追加(任意プロパティを足す):比較的安全(後方互換になりやすい)
  • ⚠️ 必須を増やす:古い利用者が壊れる可能性
  • 削除/型変更:破壊的変更になりやすい

この判断をするとき、TypeScriptの型は「契約の変化」を早めに気づかせてくれるよ👀✨


10. ミニ演習✍️🧩(手を動かすと定着するよ〜!)

演習①:型だけで防げない例を1つ書こう📝😵‍💫

次のテーマから1つ選んで「型があっても壊れる例」を書いてみてね👇

  • JSON.parse の型事故
  • localStorageの文字列事故
  • APIが仕様変更して形が変わった事故

💡ヒント:**unknownで受けて、絞り込み(型ガード)**まで書けたら満点💯✨

演習②:exactOptionalPropertyTypes 体感💣➡️💡

// tsconfig.json(抜粋)
{
"compilerOptions": {
"strict": true,
"exactOptionalPropertyTypes": true
}
}

この設定で、{ nickname: undefined } がエラーになるのを確認してみよう👀✨ (TypeScript)


11. AI活用コーナー🧠🤖✨(レビュー係にしてラクしよ)

11-1. 契約を言語化してもらう📝

  • 「この関数の利用者との約束(入力/出力/例外/副作用)を箇条書きにして」🧾✨
  • 「この型の弱点(実行時に守れない点)を3つ挙げて」⚠️

11-2. unknown→安全な型ガードを生成してもらう🔒

  • 「このJSONの想定スキーマを元に、TypeScriptの型ガード関数を書いて」🧩
  • 「境界でunknownを検証して、内部は信頼する設計にリファクタして」🚪✨

11-3. “破壊的変更っぽい?”を判定させる🔍

  • 「この型変更は後方互換? 破壊的? 理由も」🧠

(AI拡張の例:GitHubのCopilotや、OpenAI系のコーディング支援ツールなど🧸✨)


12. まとめ🎀✅

  • 型は 契約の入り口:呼び方・返し方・データ形状を“約束”できる🤝
  • でも 実行時は守れない:外から来る値は unknown で受けて検証する🚪🔒
  • exactOptionalPropertyTypes は「省略」と「undefined」を分けて契約を強くできる🧾✨ (TypeScript)
  • satisfies は「合ってるかだけ検査」できて便利🎀 (TypeScript)

参考:最新のTypeScript周辺メモ🗞️✨

  • 現時点の安定版としては npm / GitHub 上で TypeScript 5.9.3 が確認できるよ📌 (npmjs.com)
  • TypeScript 5.9 では import defer など新機能も入ってるよ📦✨ (TypeScript)
  • さらに将来に向けて、ネイティブ版のプレビューや移行ロードマップも公式から出てるよ(高速化の流れ)🚀✨ (Microsoft for Developers)