外部の値はunknownから始める
anyは型チェックを止め、存在しない操作も許してしまいます。まだ型が分からない値にはunknownを使い、調べてから利用します。
function showLength(value: unknown): void {
if (typeof value === "string") {
console.log(value.length);
} else {
console.log("文字列ではありません");
}
}
showLength("TypeScript");
showLength(100);10
文字列ではありません型ガードを関数にする
オブジェクトはtypeof nullも"object"になるため、nullを除外してプロパティを確認します。型述語value is Userは、真のときの型をコンパイラへ伝えます。
type User = { id: number; name: string };
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) return false;
const item = value as Record<string, unknown>;
return typeof item.id === "number" && typeof item.name === "string";
}
const input: unknown = { id: 1, name: "Mity" };
console.log(isUser(input) ? input.name : "不正なデータ");Mityvalue as 型は、値を実行時に変換せず、コンパイラへ「この型として扱う」と伝える型アサーションです。この例のas Record<string, unknown>で、文字列のプロパティ名を使って値を一つずつ検査できる形にしています。
as Userと書くだけでは、実行時の値がUserか確認されません。JSONやフォームなど外部入力は、実際の条件分岐で検証します。判別可能なユニオンで状態を表す
共通のstatusへ異なるリテラル値を持たせると、状態ごとに必要なデータを安全に扱えます。
type LoadState =
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; message: string };
function render(state: LoadState): string {
switch (state.status) {
case "loading": return "読込中";
case "success": return `${state.data.length}件`;
case "error": return `失敗: ${state.message}`;
}
}
console.log(render({ status: "success", data: ["A", "B"] }));2件「読込中なのにdataもある」といった矛盾した状態を型で作れなくできます。
リテラル型とas const
特定の文字列だけを許可すると、タイプミスを実行前に検出できます。通常の配列はstring[]と推論されますが、as constを付けると、各要素を変更できない具体的な値"admin"、"editor"、"viewer"として保ちます。
const roles = ["admin", "editor", "viewer"] as const;
type Role = (typeof roles)[number];
function canEdit(role: Role): boolean {
return role === "admin" || role === "editor";
}
console.log(canEdit("editor"));
// canEdit("guest"); // 型エラーtruetypeof rolesは配列全体の型を取得し、[number]は数値でアクセスしたときに得られる要素の型を取り出します。そのためRoleは、3つの文字列リテラル型を|で結んだ"admin" | "editor" | "viewer"になります。
ジェネリクスで型の関係を保つ
型引数Tを使うと、具体的な型を呼び出し時に決められます。anyと違い、入力と出力の関係が失われません。
function first<T>(items: T[]): T | undefined {
return items[0];
}
const name = first(["Aki", "Mika"]);
const score = first([80, 95]);
console.log(name);
console.log(score);Aki
80空配列では値がないため、戻り値にundefinedを含めています。
noUncheckedIndexedAccessの設定を有効にすると、items[0]にもundefinedの可能性が反映されます。この戻り値は、その可能性を呼び出し側へ正しく伝える設計です。keyofで存在するキーに限定する
function getProperty<T extends object, K extends keyof T>(
object: T,
key: K,
): T[K] {
return object[key];
}
const product = { name: "ノート", price: 200 };
console.log(getProperty(product, "price"));
// getProperty(product, "stock"); // 型エラー200neverで分岐漏れを検出する
すべての状態を処理した後の値はneverです。新しい状態を追加したのにswitchを直し忘れると型エラーにできます。
type Size = "small" | "large";
function price(size: Size): number {
switch (size) {
case "small": return 300;
case "large": return 500;
default: {
const unreachable: never = size;
return unreachable;
}
}
}
console.log(price("large"));500ミニ課題:API結果を表す
idle、loading、success、errorの4状態を判別可能なユニオンで定義し、すべての状態を文字列に変換してください。successだけは商品名の配列、errorだけはメッセージを持たせます。
