プリミティブ型 — 型システムの基盤

TypeScriptの型システムは7つのプリミティブ型を基盤としています。 これらはJavaScriptのプリミティブ値と1対1で対応します。

説明
string 文字列 "hello", `template`
number 数値(整数・浮動小数点の区別なし) 42, 3.14
boolean 真偽値 true, false
null 値が存在しない null
undefined 値が未定義 undefined
bigint 大きな整数 9007199254740991n
symbol 一意な識別子 Symbol("id")
// 型注釈の基本
const name: string = "TypeScript";
const version: number = 6.0;
const isStable: boolean = true;
const nothing: null = null;
const notDefined: undefined = undefined;

// 型推論 — 初期値から自動的に型が決まる
const language = "TypeScript"; // string と推論
const year = 2026;             // number と推論

ユニオン型とインターセクション型

ユニオン型(|)— 「AまたはB」

ユニオン型は、値が複数の型のいずれかであることを表します。 APIレスポンスの処理やイベントハンドラなど、実務で最も頻繁に使う型の一つです。

// 基本的なユニオン型
function formatId(id: string | number): string {
  if (typeof id === "string") {
    return id.toUpperCase();  // string として扱える
  }
  return id.toString();       // number として扱える
}

formatId("abc-123"); // "ABC-123"
formatId(42);         // "42"

// リテラル型のユニオン — 特定の値のみを許可
type Status = "loading" | "success" | "error";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

インターセクション型(&)— 「AかつB」

インターセクション型は、値が複数の型をすべて満たすことを表します。 型の合成(Composition)に使われ、ミックスインパターンの実装に適しています。

type HasName = { name: string };
type HasAge = { age: number };
type HasEmail = { email: string };

// 3つの型を合成
type Person = HasName & HasAge & HasEmail;

const user: Person = {
  name: "太郎",
  age: 30,
  email: "taro@example.com",
  // 3つのプロパティすべてが必要
};

any / unknown / never — 型システムの境界

TypeScriptの型システムには、特殊な役割を持つ3つの型があります。 これらの違いを理解することは、安全なTypeScriptコードを書くための基本です。

graph TD
  A[any / unknown\nトップ型\nすべての型を含む] --> B[string]
  A --> C[number]
  A --> D[boolean]
  A --> E[...]
  B --> F[never\nボトム型\nどの型にも属さない]
  C --> F
  D --> F
  E --> F

  style A fill:#ef4444,stroke:#dc2626,color:#fff
  style F fill:#6b7280,stroke:#4b5563,color:#fff
  style B fill:#3b82f6,stroke:#2563eb,color:#fff
  style C fill:#3b82f6,stroke:#2563eb,color:#fff
  style D fill:#3b82f6,stroke:#2563eb,color:#fff
  style E fill:#3b82f6,stroke:#2563eb,color:#fff
TypeScriptの型階層 — any/unknownがトップ(全てを含む)、neverがボトム(何も含まない)
位置 代入可能性 使用前の要件 推奨度
any トップ型 何でも代入可能、何にでも代入可能 なし(型チェック無効化) ❌ 非推奨
unknown トップ型 何でも代入可能だが、使用前に型ガード必須 型ガードで絞り込み ✅ 推奨
never ボトム型 どの型にも代入不可 到達不能コード 自然に出現
// ❌ any: 危険 — 型安全性が完全に失われる
const risky: any = "hello";
risky.nonExistent();  // コンパイル通るが実行時エラー!
risky.toFixed(2);     // コンパイル通るが実行時エラー!

// ✅ unknown: 安全 — 使用前にチェックが必須
const safe: unknown = "hello";
// safe.toUpperCase(); // コンパイルエラー!型ガードが必要

if (typeof safe === "string") {
  safe.toUpperCase();  // OK: 型ガード後は string として扱える
}

// never: 到達不能を表す
function throwError(msg: string): never {
  throw new Error(msg); // この関数は決して正常にreturnしない
}

型ガード — 型を安全に絞り込む

型ガードは、条件分岐を使ってユニオン型をより具体的な型に絞り込む仕組みです。 第3章で学んだCheckerの制御フロー解析がこの機能を支えています。

手法 用途 構文例
typeof プリミティブ型の判定 typeof x === "string"
instanceof クラスインスタンスの判定 x instanceof Date
"prop" in obj プロパティの存在確認 "name" in obj
真偽値チェック null/undefinedの除外 if (x)
等値チェック リテラル型の一致確認 x === null
Type Predicate カスタム型ガード関数 x is Type
// typeof ガード
function processValue(value: string | number | boolean) {
  if (typeof value === "string") {
    return value.trim();     // string
  } else if (typeof value === "number") {
    return value.toFixed(2); // number
  } else {
    return value.toString(); // boolean
  }
}

// カスタム型ガード関数(Type Predicate)
interface Fish { swim: () => void }
interface Bird { fly: () => void }

function isFish(animal: Fish | Bird): animal is Fish {
  return "swim" in animal;
}

function move(animal: Fish | Bird) {
  if (isFish(animal)) {
    animal.swim(); // Fish として扱える
  } else {
    animal.fly();  // Bird として扱える
  }
}

Discriminated Union — 最も実用的なパターン

Discriminated Union(判別共用体)は、TypeScriptで最も頻繁に使われる型パターンの一つです。 共通の「判別プロパティ」を持つユニオン型で、switch文による網羅的な処理を可能にします。

// APIレスポンスをDiscriminated Unionで表現
type ApiResponse =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function handleResponse(response: ApiResponse) {
  switch (response.status) {
    case "loading":
      return "読み込み中...";
    case "success":
      return response.data.join(", "); // data にアクセス可能
    case "error":
      return `エラー: ${response.message}`; // message にアクセス可能
  }
}

never型による網羅性チェック

never型を活用すると、switch文ですべてのケースを処理したかをコンパイル時に検証できます。 新しいステータスが追加された際に、処理の追加漏れをコンパイルエラーとして検出できます。

function assertNever(x: never): never {
  throw new Error(`予期しない値: ${x}`);
}

function handleResponse(response: ApiResponse) {
  switch (response.status) {
    case "loading":
      return "読み込み中...";
    case "success":
      return response.data.join(", ");
    case "error":
      return `エラー: ${response.message}`;
    default:
      // すべてのケースを処理していれば、ここには到達しない
      // 処理漏れがあれば、コンパイルエラーになる
      return assertNever(response);
  }
}

理解度チェック

問題 0 / 50%
Q1

外部APIから受け取った未知のデータを安全に扱うために最適な型はどれですか?

キーボード: 1〜4 で選択、Enter で回答