第4章:魔法の文字列を卒業!定数・辞書・ユニオン型でDRY🏷️✨
4-1. 「魔法の文字列」ってなに?🪄😵

たとえば、アプリのあちこちにこんなのが散らばってる状態👇
- "paid" / "pending" / "canceled"
- "user" / "admin"
- "theme"(localStorageのキー)
- "/api/orders"(URL)
- "SUBMIT_CLICK"(イベント名)
これ、いわゆる 魔法の文字列(意味を持った文字列がベタ書きで散らばってる状態) だよ〜😇💦 ヤバい理由はシンプル!
- タイポが死ぬほど怖い("paied" とか…気づきにくい😱)
- 変更が地獄(仕様で "pending" → "awaiting" に変わったら全箇所探す羽目👀💥)
- 同じ意味が複数の表現になる("PAID" と "paid" が混在して混乱🤯)
ここでDRYの出番!✨ 「同じ知識(=状態名、キー名、URLなど)を1か所に集める」ことをやるよ💪💖
4-2. この章のゴール🎯💗
この章が終わると、こうなれるよ👇✨
- 文字列コピペをやめて、定数でまとめられる📌
- 対応表(ステータス→表示名など)を 辞書(Record) で1か所管理できる🗂️
- 使える値を ユニオン型 で縛って、タイポをコンパイルで止められる🛡️✨
ちなみに今のTypeScript最新版は 5.9 系が “latest” 扱いになってるよ(npmでも確認できる)📦✨ (npmjs.com)
4-3. まずは定数で「散らばり」を止血しよう🩹📌
パターンA:とにかく一旦まとめる(超現実的)😌
1か所に集めるだけでも、事故率が激減するよ✨
// constants/status.ts
export const STATUS_PAID = "paid";
export const STATUS_PENDING = "pending";
export const STATUS_CANCELED = "canceled";
ただし!これだけだと、TypeScript的にはただの string 扱いになりやすい(型が強くならない)ことがあるの🥺 そこで次!
4-4. as const で「リテラル型」に固定しよう🧷✨
パターンB:定数の“集合”を作る(おすすめ💖)
オブジェクトにまとめて、最後に as const を付けるのが超よくある型安全パターンだよ〜!✨ as const は「型の世界でも定数っぽく固定する」ための仕組みだよ🧠✨ (TypeScript)
// domain/status.ts
export const OrderStatus = {
Paid: "paid",
Pending: "pending",
Canceled: "canceled",
} as const;
これで何が嬉しいの?😊 OrderStatus.Paid の型が "paid" になって、ただの string じゃなくなるの!🎉
4-5. ユニオン型で「使っていい値」だけに縛る🛡️💫

次に、OrderStatus からユニオン型を自動生成しちゃうよ✨
// domain/status.ts
export const OrderStatus = {
Paid: "paid",
Pending: "pending",
Canceled: "canceled",
} as const;
export type OrderStatusValue = typeof OrderStatus[keyof typeof OrderStatus];
これで OrderStatusValue はこうなる👇 "paid" | "pending" | "canceled"
文字列リテラル型+ユニオン型って、文字列で enum っぽいことができる王道のやつ!😆✨ (TypeScript)
使う側はこうなる💖
import { OrderStatus, type OrderStatusValue } from "./domain/status";
function setStatus(orderId: string, status: OrderStatusValue) {
// ...
}
setStatus("A001", OrderStatus.Paid); // OK 😊
setStatus("A001", "paied"); // コンパイルで止まる😇✨
4-6. 辞書(Record)で「対応表」を1か所にまとめる🗂️✨
次は「ステータス→表示名」みたいな対応表あるあるね!💡
例:表示ラベルを辞書化する📛✨
import { type OrderStatusValue } from "./domain/status";
export const StatusLabel: Record<OrderStatusValue, string> = {
paid: "支払い済み",
pending: "保留中",
canceled: "キャンセル",
};
ここが気持ちいいポイント💖
- ステータスを増やしたら、辞書側が 足りない って怒られる😆
- タイポキー("pendng" とか)も弾ける🛡️
Record は「キーの集合」と「値の型」をセットで縛れる便利ユーティリティ型だよ✨ (TypeScript)
4-7. satisfies で「辞書の型チェック」と「推論の美味しさ」を両取り🍰✨
TypeScriptには satisfies って演算子があって、 「型に合ってるか検証するけど、値そのものの型推論は崩さない」っていう良いとこ取りができるよ〜!😳✨ (TypeScript)
import { type OrderStatusValue } from "./domain/status";
export const StatusLabel = {
paid: "支払い済み",
pending: "保留中",
canceled: "キャンセル",
} satisfies Record<OrderStatusValue, string>;
これ、地味に便利なのは👇
- 「Recordとして成立してるか」はチェックされる✅
- でも StatusLabel.paid は "支払い済み"(リテラル)として残りやすい✨ → あとで別の型推論に使いたい時に嬉しい💖
4-8. “変更に強い”DRYにするコツ(超大事)🧠🔧
✅ コツ1:値をベタ書きしたくなったら「それ、知識?」って自問する🤔
- ただの表示用文章? → その場でもOKなこと多い
- 状態・キー・URL・イベント名? → だいたい「知識」なので1か所へ🏷️✨
✅ コツ2:同じ理由で変わるもの同士をまとめる📦
- OrderStatus(注文の状態)
- UserRole(権限)
- StorageKey(localStorageのキー)
- ApiPath(APIパス)
「なんでもconstants.ts」みたいに巨大化させると迷子になるから、ドメインごとにファイル分けがオススメだよ〜🗺️💕
4-9. ミニ演習💪💖(ステータス散らばりを救え!)
お題😆📝
こんなコードがあるとする👇
function canCancel(status: string) {
return status === "pending";
}
function renderBadge(status: string) {
if (status === "paid") return "✅";
if (status === "pending") return "🕒";
if (status === "canceled") return "❌";
return "?";
}
やること🎯
- OrderStatus(as const)を作る
- OrderStatusValue を作る
- canCancel の引数を OrderStatusValue にする
- バッジを辞書(Record or satisfies)にする
できあがり例(答えの一例)✨
export const OrderStatus = {
Paid: "paid",
Pending: "pending",
Canceled: "canceled",
} as const;
export type OrderStatusValue = typeof OrderStatus[keyof typeof OrderStatus];
export function canCancel(status: OrderStatusValue) {
return status === OrderStatus.Pending;
}
export const StatusBadge = {
paid: "✅",
pending: "🕒",
canceled: "❌",
} satisfies Record<OrderStatusValue, string>;
export function renderBadge(status: OrderStatusValue) {
return StatusBadge[status];
}
4-10. AI活用🤖💖(“いい感じにDRY化”させる質問テンプレ)
Copilot/AIに投げると強い聞き方を置いとくね✨ (答えを丸のみせず、最後は自分で「知識が1か所になってる?」をチェック!🧠)
- 「このファイル内の magic string を列挙して、定数化候補を提案して」
- 「as const のオブジェクト+ユニオン型にして、既存コードが壊れないように最小変更で直して」
- 「Record を使って、キーの漏れがコンパイルで検出できる形にして」
- 「satisfies を使う版と使わない版で、推論の違いも説明して」
GitHub Copilotは、提案・チャット・検索系の支援ができるよ(機能の全体像)🧩✨ (GitHub Docs)
4-11. 章まとめ🎀✨
この章の合言葉はこれっ👇😆💕
- **文字列が“知識”なら、散らすな!1か所に置け!**🏷️
- **as const + ユニオン型で、タイポをコンパイルで止める!**🛡️
- **Record / satisfies で、対応表の漏れをコンパイルで炙り出す!**🔥
次の章(型でDRY🧠🧱)に行くと、ここで作った「型安全な定義」をもっと強く使い回せるようになるよ〜!💖