TypeScript

型を使いこなす

値が取り得る状態を正確に表し、危険な入力を安全な値へ変えましょう。

01

外部の値はunknownから始める

anyは型チェックを止め、存在しない操作も許してしまいます。まだ型が分からない値にはunknownを使い、調べてから利用します。

値を調べるTYPESCRIPT
function showLength(value: unknown): void {
  if (typeof value === "string") {
    console.log(value.length);
  } else {
    console.log("文字列ではありません");
  }
}

showLength("TypeScript");
showLength(100);
実行結果OUTPUT
10
文字列ではありません
02

型ガードを関数にする

オブジェクトはtypeof null"object"になるため、nullを除外してプロパティを確認します。型述語value is Userは、真のときの型をコンパイラへ伝えます。

Userか確認TYPESCRIPT
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 : "不正なデータ");
実行結果OUTPUT
Mity

value as 型は、値を実行時に変換せず、コンパイラへ「この型として扱う」と伝える型アサーションです。この例のas Record<string, unknown>で、文字列のプロパティ名を使って値を一つずつ検査できる形にしています。

型アサーションは検査ではない as Userと書くだけでは、実行時の値がUserか確認されません。JSONやフォームなど外部入力は、実際の条件分岐で検証します。
03

判別可能なユニオンで状態を表す

共通のstatusへ異なるリテラル値を持たせると、状態ごとに必要なデータを安全に扱えます。

読込状態TYPESCRIPT
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"] }));
実行結果OUTPUT
2件

「読込中なのにdataもある」といった矛盾した状態を型で作れなくできます。

04

リテラル型とas const

特定の文字列だけを許可すると、タイプミスを実行前に検出できます。通常の配列はstring[]と推論されますが、as constを付けると、各要素を変更できない具体的な値"admin""editor""viewer"として保ちます。

選択肢から型を作るTYPESCRIPT
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"); // 型エラー
実行結果OUTPUT
true

typeof rolesは配列全体の型を取得し、[number]は数値でアクセスしたときに得られる要素の型を取り出します。そのためRoleは、3つの文字列リテラル型を|で結んだ"admin" | "editor" | "viewer"になります。

05

ジェネリクスで型の関係を保つ

型引数Tを使うと、具体的な型を呼び出し時に決められます。anyと違い、入力と出力の関係が失われません。

先頭の値を返すTYPESCRIPT
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);
実行結果OUTPUT
Aki
80

空配列では値がないため、戻り値にundefinedを含めています。

厳格な配列アクセスと相性がよい設計 後で扱うnoUncheckedIndexedAccessの設定を有効にすると、items[0]にもundefinedの可能性が反映されます。この戻り値は、その可能性を呼び出し側へ正しく伝える設計です。
06

keyofで存在するキーに限定する

プロパティを安全に取得TYPESCRIPT
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"); // 型エラー
実行結果OUTPUT
200
07

neverで分岐漏れを検出する

すべての状態を処理した後の値はneverです。新しい状態を追加したのにswitchを直し忘れると型エラーにできます。

網羅性を確認TYPESCRIPT
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"));
実行結果OUTPUT
500
PRACTICE

ミニ課題:API結果を表す

idleloadingsuccesserrorの4状態を判別可能なユニオンで定義し、すべての状態を文字列に変換してください。successだけは商品名の配列、errorだけはメッセージを持たせます。

確認 存在しない状態や、successなのに商品配列がない値を作ると型エラーになることを確かめましょう。