🧩

【初学者向け】TypeScriptで網羅性チェックを活用しよう

2024/12/02に公開

本記事では、TypeScript初学者に向けて、網羅性チェックの重要性とその具体的な実装方法を解説します。

網羅性チェックとは

網羅性チェック(Exhaustiveness Checking)とは、コード内で型のすべてのケースが適切に処理されていることをコンパイラが保証する仕組みです。

網羅性チェックの重要性

  • 予期せぬバグの防止
    • コードに未処理のケースが存在したときに、コンパイラが警告を出してくれる
  • 保守性の向上
    • 仕様変更時に未処理のケースがあれば、コンパイラの警告で気付くことができる
    • 型のすべてのケースを明示的に扱うことで、コードの意図が分かりやすくなる

網羅性チェックが無い場合のリスク

以下の例では、網羅性チェックが無いためにケースが漏れている状態が発生しています。

type Status = 'success' | 'error' | 'loading'

function handleStatus(status: Status) {
  if (status === 'success') {
    console.log('成功')
  } else if (status === 'error') {
    console.log('エラー')
  }
  // "loading" のケースが考慮されていない
}

このコードでは"loading"のケースの考慮が漏れており、意図しない挙動を引き起こす可能性があります。

網羅性チェックの実現方法

never型を使ったチェック

TypeScriptでは、never型を使用して網羅性チェックを行います。

never型について

本記事ではnever型自体の解説は取り扱いません。詳しく知りたい方は以下の記事が分かりやすいのでおすすめです。

https://typescriptbook.jp/reference/statements/never

never型は「値を持たない」を意味するTypeScriptの特別な型です。

以下の例では、"loading"のケースを考慮していないため、default節でエラーが発生します。

type Status = 'success' | 'error' | 'loading'

function handleStatus(status: Status) {
  switch (status) {
    case 'success':
      console.log('成功')
      break
    case 'error':
      console.log('エラー')
      break
    default:
      assertNever(status)
    // 型 'string' の引数を型 'never' のパラメーターに割り当てることはできません。
  }
}

function assertNever(value: never): never {
  throw new Error('Unexpected value: ' + value)
}

satisfies演算子を使用した簡略化

TypeScript 4.4以降ではsatisfies演算子を使うことで、網羅性のチェックを簡略化できます。

type Status = 'success' | 'error' | 'loading'

function handleStatus(status: Status) {
  switch (status) {
    case 'success':
      console.log('成功')
      break
    case 'error':
      console.log('エラー')
      break
    default:
      throw new Error(status satisfies never)
    // 型 'string' は想定された型 'never' を満たしていません。
  }
}

Record型を使ったチェック

switch文を使用せず、Record型を用いてパターンごとの処理を定義する方法もあります。
この方法では、全てのケースを定義しないとコンパイルエラーが発生します。

type Status = 'success' | 'error' | 'loading'

const handleStatus = (status: Status): void => {
  // プロパティ 'loading' は型 '{ success: () => void; error: () => void; }' にありませんが、型 'Record<Status, () => void>' では必須です。
  const handlers: Record<Status, () => void> = {
    success: () => console.log('成功しました!'),
    error: () => console.log('エラーが発生しました。'),
  }

  handlers[status]()
}

参考文献

https://typescriptbook.jp/reference/statements/never
https://azukiazusa.dev/blog/exhaustive-checks-in-typescript/

Aprender Tech Blog

Discussion