【入門編】TypeScriptの型システムにおける「トップ型」と「ボトム型」の活用 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!TypeScriptの型システムの世界へようこそ。
他のオブジェクト指向言語(JavaやC#、あるいは動的言語のJavaScriptなど)からTypeScriptに入ってきたとき、最初に「おっ?」と立ち止まるポイントが、この「トップ型」と「ボトム型」の概念なんですよね。

`unknown`、`any`、そして `never`。
これらの型は、TypeScriptの型システム全体をピラミッドのように支える、いわば「超重要人物」たちです。ここをしっかりクリアすれば、TypeScriptの型安全の仕組みはバッチリマスターできますよ。

今日は、インターフェースや型エイリアスと組み合わせながら、現場で即座に役立つ「堅牢なエラーハンドリング」を実装する方法を、一緒に紐解いていきましょう!

—

1. 型のピラミッド構造をイメージしよう

TypeScriptの型システムは、すべての型を網羅する巨大な「型の階層構造(包含関係)」を持っています。
まずは、頭の中で次のようなピラミッドをイメージしてみてください。

[ トップ型 ] ← すべてを包み込む神の領域 (unknown, any)
↓
(通常の型たち) ← string, number, Userインターフェースなど
↓
[ ボトム型 ] ← 何も存在しない虚無の領域 (never)

このピラミッドの頂点に君臨するのが「トップ型」、そして最底辺に位置するのが「ボトム型」です。それぞれの特徴を優しく見ていきましょう。

—

2. トップ型:すべてを受け入れる懐の深さ(`unknown` と `any`)

トップ型とは、「あらゆる型の子孫(代入可能な値)」を意味します。つまり、どんな値であっても、トップ型の変数に格納することができます。

ここに登場するのが、おなじみの `any` と、少し厳格な親戚である `unknown` です。

ざっくり言うと:

  • `any`: 「型チェックを放棄する禁断の呪文」。何でも許しますが、TypeScriptの恩恵(補完や安全性)がすべて消え去ります。
  • `unknown`: 「安全な何者か分からないもの」。何でも受け入れますが、使うときは必ず型を絞り込まないと怒られます。

インターフェースや型エイリアスでの実践例

例えば、外部のAPIから帰ってきたレスポンスデータを扱う型エイリアスを考えてみましょう。

type ApiResponse = {
status: number;
// レスポンスのデータ構造が状況によって変わるため、ここでは一旦トップ型にする
data: unknown;
};

const response: ApiResponse = {
status: 200,
payload: { userId: “123”, name: “Alice” } // どんなデータも入る
};

ここで `response.data` をそのまま使おうとすると、TypeScriptのコンパイラはこう言います。
> 「おいおい、`unknown`型の中身が何だか分からないのに、勝手にプロパティにアクセスしちゃダメだよ!」

これが `unknown` の優しさです。これを使うには、「型ガード(Narrowing)」という手順を踏む必要があります。

function handleResponse(res: ApiResponse) {
// 安全確認(型チェック)を行う
if (typeof res.data === “object” && res.data !== null && “name” in res.data) {
// このブロックの中に入った瞬間、TypeScriptは `res.data` が何者かを理解する!
console.log((res.data as { name: string }).name);
} else {
console.error(“予期せぬデータ構造です”);
}
}

実務では、バグの温床になる `any` は極力封印し、安全に型を検証できる `unknown` を使っていくのがモダンなプロフェッショナルの作法です。

—

3. ボトム型:何も存在しない虚無の領域(`never`)

逆に、ピラミッドの最底辺に位置するのがボトム型である `never` です。
`never` は、「決して起こり得ない値の型」を意味します。

「何も値が入らないなら、何の役に立つの?」と思いますよね。実はこの `never` こそが、堅牢なエラーハンドリングや網羅性チェックにおいて「最強の武器」になるんです。

陥りがちな誤解:`void` との違い

  • `void` は、「何も返さない関数が返す型」(undefinedに近い)。
  • `never` は、「そもそも正常に終了しない(あるいは絶対に値が存在しない)」ことを表す型。

—

4. 実践:`unknown` と `never` で作る最強のエラーハンドリング

それでは、ここまでの知識を総動員して、実務でそのまま使える「堅牢なエラーハンドリング」を書いてみましょう。

よくある、APIリクエストで失敗したときのエラーをキャッチするコードです。

// 1. エラーメッセージを表現する型エイリアス
type AppError = {
code: string;
message: string;
};

// 2. 予期せぬエラーを安全に処理する関数
function handleError(error: unknown): void {
// unknown型なので、まずErrorオブジェクトかどうかをガードする
if (error instanceof Error) {
console.error(`通常のエラー: ${error.message}`);
}
// 自作のAppError構造体かチェック
else if (isAppError(error)) {
console.error(`アプリ固有エラー [${error.code}]: ${error.message}`);
}
// それ以外の完全に正体不明の何か(文字列や数値がthrowされた場合など)
else {
// ここでボトム型 `never` が真価を発揮する!
// もし全てのパターンを網羅していれば、この変数に入る値は「存在しない(never)」はず
const exhaustiveCheck: never = error;
console.error(“未知の致命的エラー:”, exhaustiveCheck);
}
}

// 型ガード用のヘルパー関数
function isAppError(arg: unknown): arg is AppError {
return (
typeof arg === “object” &&
arg !== null &&
“code” in arg &&
typeof (arg as AppError).code === “string” &&
“message” in arg &&
typeof (arg as AppError).message === “string”
);
}

ここがプロの技!網羅性チェック(Exhaustive Check)

例えば、次のような「処理の状態(ステータス)」を表す型があったとします。

type Status = “loading” | “success” | “error”;

function handleStatus(status: Status) {
switch (status) {
case “loading”:
return “読み込み中…”;
case “success”:
return “成功!”;
case “error”:
return “失敗…”;
default:
// 【重要】もし将来、Status型に “timeout” が追加されたのに、
// このswitch文に書き忘れた場合、TypeScriptはここでコンパイルエラーを出してくれます!
const _exhaustiveCheck: never = status;
return _exhaustiveCheck;
}
}

もし `status` に `”timeout”` が追加され、`case “timeout”:` の書き忘れが発生した場合、`default` ブロック内の `status` はもはや `never` ではなく `”timeout”`型になってしまいます。
「`timeout` という値を、`never` 型の変数には入れられません!」というコンパイルエラーが起きるため、バグが本番環境に出る前に、コードを書いている段階で気づくことができるのです。これが `never` 型の最高の活用法です。

—

まとめ

  • トップ型 (`unknown`): 「何が来るか分からない」を安全に扱うための入り口。使うときは必ず型ガードを通す。
  • ボトム型 (`never`): 「絶対にあり得ない」を表現し、コンパイラに網羅性を強制させるための切り札。

この2つをインターフェースや型エイリアスと組み合わせることで、TypeScriptの型システムは単なる「文字の補完ツール」から、あなたのコードベースを守り抜く「強固な防壁」へと進化します。

ここをクリアできれば、TypeScriptの基本はバッチリマスターできていますよ!自信を持って、次のステップへ進んでくださいね。それではまた!

タイトルとURLをコピーしました