👌

TypeScriptの特別な型たちを徹底解説!`unknown`、`any`、`void`、`never`の使い方と選び方

2024/12/01に公開

TypeScriptの特別な型たちを徹底解説!unknownanyvoidneverの使い方と選び方

TypeScriptには、特別な役割を持つ型としてunknownanyvoidneverがあります。それぞれの型を理解することで、より安全で堅牢なコードを書くことが可能です。本記事では、これらの型の概要、具体的な使い方、実務での使用例について簡潔に説明します。

型の概要(ざっくりと理解)

フローチャートでの比較

下記のフローチャートは、どの型を選ぶべきかを視覚的に説明しています。コードの状況や用途に応じて適切な型を選ぶ際の参考にしてください。

  • unknown: 型が不明な値を保持するが、利用時には型チェックが必要。安全性が高い。
  • any: どんな型でも受け入れるが、型チェックが行われないため安全性が低い。
  • void: 関数が値を返さないことを示す。戻り値のない関数に使う。
  • never: 決して値を返さないことを示す。常に例外をスローする関数や無限ループに使う。

unknown

概要

メリットとデメリット

  • メリット: 型が不明な場合でも安全に操作できる。型チェックを強制することでコードの安全性を向上させる。
  • デメリット: 使用する前に型チェックが必要なため、コードが少し冗長になる可能性がある。

unknown 型は、どのような型の値が入るか分からない場合に使用する「未知の型」です。unknownはTypeScriptにおいて、どんな値でも持つことができますが、安全に操作するためには型チェックが必要です。

let value: unknown;
value = "Hello";

if (typeof value === "string") {
  console.log(value.toUpperCase()); // 型チェック後なので安全に文字列操作できる
}

別の例として、unknown 型を引数に取る関数があります。

function processValue(value: unknown) {
  if (typeof value === "number") {
    console.log(value + 1); // 数値であることを確認後に操作
  } else if (typeof value === "string") {
    console.log(value.toUpperCase()); // 文字列であることを確認後に操作
  } else {
    console.log("値が不明です");
  }
}

ユースケースと実用性

  • APIからのレスポンス: サーバーからのレスポンスの型が分からない場合、一旦 unknown 型で受け取ることで安全に処理できます。
  • ユーザー入力の取り扱い: ユーザーからの入力値の型が不明な場合に利用し、利用前に型チェックを行うことで型安全を確保します。
  • プラグイン開発: 外部からの拡張機能(プラグイン)の入力データ型が不明な場合、unknownを使用することで安全に取り扱い、プラグインの不正な動作を防止します。

ガイドライン

  • 安全性のために型チェックを強制: unknown型を使うことで、実際に値を利用する際に型チェックを強制し、より安全なコードになります。
  • anyの代わりに利用: unknownを使うことで、安全性を確保しながら柔軟に対応できます。

any

概要

メリットとデメリット

  • メリット: 非常に柔軟で、どんな型でも扱えるため、プロトタイピングや型定義が未確定な場合に素早く実装できる。
  • デメリット: 型チェックが行われないため、意図しないエラーを引き起こしやすく、安全性が低い。

any 型は、どんな型でも受け入れることができる非常に柔軟な型です。しかし、any型を使用すると、型チェックが行われないため、コードの安全性が低下します。

let value: any;
value = "Hello";
value = 42;
value.foo(); // 型チェックがないため、存在しないメソッドを呼んでもエラーにならない

ユースケースと実用性

  • プロトタイプ開発: まずは型を厳密に定義せずに、手早く機能を実装したい場合に使用されることがあります。
  • 既存のJavaScriptコードの移行: JavaScriptからTypeScriptに移行する際、すべての型を即座に定義するのが難しい場合、暫定的に any を使うことがあります。
  • 型の定義が難しい場合: 非常に柔軟な構造を持つデータ(例えば、外部ライブラリから受け取るオブジェクト)に対して一時的に any を使い、後から型を定義することもあります。
  • データの一時的な保持: フォームデータの一時保存など、型を厳密に決めずに一時的にデータを扱いたい場合に any を使用し、後で適切な型を定義します。

ガイドライン

  • できる限りunknownを使う: anyを多用するとバグが発生しやすくなるため、極力 unknown や他の型を使うべきです。
  • 一時的な解決策として利用: 型が明確になるまでの一時的な対応として any を利用し、最終的には型定義を行う。

ケーススタディ: anyからunknownへの置き換え

プロジェクトでよくあるケースとして、APIからのレスポンスデータの取り扱いがあります。初期段階では、レスポンスの型が決まっていないため、anyを使ってしまいがちです。

// 悪い例: anyを使ったケース
function handleApiResponse(response: any) {
  console.log(response.name); // 型チェックがないため、存在しないプロパティにアクセスするとエラーが発生する可能性がある
}

このコードは、APIレスポンスの構造が変わった場合にエラーを見逃すリスクがあります。これをunknownに変更することで、型チェックを強制し、安全性を向上させることができます。

// 改善例: unknownを使ったケース
function handleApiResponse(response: unknown) {
  if (typeof response === "object" && response !== null && "name" in response) {
    console.log((response as { name: string }).name); // 型チェック後にプロパティにアクセスするため安全
  } else {
    console.log("無効なレスポンスです");
  }
}

この改善例では、unknownを使うことで、値を使用する前に型チェックを行い、存在しないプロパティにアクセスしないようにしています。これにより、コードの安全性が向上し、バグを防ぐことができます。

void

概要

メリットとデメリット

  • メリット: 関数が値を返さないことを明示することで、コードの意図をはっきりさせることができる。
  • デメリット: 他の型と比べて使用の幅が狭く、特に関数の戻り値に限定される。

void 型は、関数が値を返さないことを表す型です。通常、何も返さない関数の戻り値の型として使用します。

function logMessage(message: string): void {
  console.log(message);
}

また、コールバック関数の引数として void を使うこともあります。

function executeCallback(callback: () => void) {
  callback();
}

ユースケースと実用性

  • ログ関数: ログを出力するだけで値を返さない関数に使用します。
  • イベントハンドラ: ユーザーインターフェースのイベントを処理するが、戻り値を必要としない場合に使用します。
  • 非同期処理後の処理: 非同期操作が完了した後に実行されるコールバック関数など、戻り値が不要なケースで使用されます。
  • サードパーティAPIの利用: サードパーティAPIを呼び出し、その結果を使わずに完了を示すだけの関数に使用することが一般的です。

ガイドライン

  • 戻り値が不要であることを明確にする: voidを使うことで、関数が意図的に戻り値を持たないことを示し、コードの可読性を向上させます。

never

概要

メリットとデメリット

  • メリット: 到達不能なコードを明示することで、型システムを活用したバグの防止に役立つ。
  • デメリット: 非常に特殊なケースに限定されるため、意図して使う機会が少ない。

never 型は、決して値を返さないことを表す型です。例えば、関数が常に例外をスローするか、無限ループする場合に never 型を使います。

function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

ユースケースと実用性

  • エラーハンドリング関数: 常に例外をスローする関数に使用し、その関数が正常に終了しないことを示します。
  • 型の絞り込み: never は型の絞り込みの際に、到達不能なコードを示すためにも使われます。
  • データベースの異常処理: データベースの不整合や処理の異常が発生した場合に、never型の関数を用いて例外を発生させ、決して戻り値が発生しないことを明示します。
function processInput(value: string | number): void {
  if (typeof value === "string") {
    console.log("文字列です: " + value);
  } else if (typeof value === "number") {
    console.log("数値です: " + value);
  } else {
    const neverValue: never = value; // 型チェックの段階で、この行は到達不能であることを示す
    console.log(neverValue);
  }
}

ガイドライン

  • 到達不能コードの明示: neverを使うことで、到達不能なケースを明示し、型システムを利用してバグを防ぐことができます。

unknownanyvoidnever の違い

説明 ユースケース 安全性 使用頻度(実務) 目撃頻度
unknown どんな型でも代入可能だが、利用時に型チェックが必要 APIからのデータなど、型が分からないとき
any どんな型でも代入でき、型チェックを行わない 型チェックを一時的に無効化したいとき 低(でありたい)
void 関数が値を返さないことを示す 戻り値のない関数
never 決して終了しない、または例外をスローする関数を示す 例外をスローする関数や無限ループ

違いを理解することの重要性

TypeScriptでは、これらの型を適切に使い分けることで、コードの安全性と可読性を高めることができます。anyを多用することで得られる柔軟性は魅力的ですが、それによって型安全性が失われてしまうため、できる限りunknownや他の型を使うべきです。また、voidneverを使うことで関数の意図を明確にすることができ、コードの理解が容易になります。

これらの型を使いこなすことで、TypeScriptの持つ型システムの強力な恩恵を最大限に活かすことができます。初心者の方も、少しずつこれらの型に慣れていくことで、より堅牢で保守性の高いコードを書けるようになるでしょう。

株式会社くりぼー

Discussion