問題点
標準的なtry-catchブロックでエラーメッセージをログ出力しようとしているのに、TypeScriptが赤い波線を表示して止めてしまうことがあります。以下のようなコードです。
try {
// 危険なロジック
} catch (error) {
console.error(error.message); // Error: Object is of type 'unknown'. (TS18046)
}
これは、近年のTypeScript(バージョン4.0から導入され、4.4以降でより厳格化)において、catch変数のデフォルトがanyではなくunknownになったためです。これは保護策です。JavaScriptでは、throw文は文字列、数値、あるいは{ code: 500 }のようなオブジェクトリテラルなど、あらゆるものを返すことができます。コンパイラはエラーが実際のErrorオブジェクトであることを保証できないため、実行時にアプリがクラッシュするのを防ぐために、.messageへのアクセスを制限しているのです。
TS18046の解決方法
1. 推奨される方法:'instanceof' 型ガード
コンパイラを納得させる最もクリーンな方法は、エラーの正体を確認することです。オブジェクトが組み込みのErrorクラスのインスタンスであるかどうかをチェックすることで、TypeScriptは自動的に型を絞り込み(narrowing)、messageやstackへの安全なアクセスを許可します。
try {
// 処理を実行
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error("An unexpected non-error object was thrown", error);
}
}
このアプローチは、ライブラリが適切なErrorオブジェクトではなく、単なる文字列をスローするような稀なケースにも対応できます。
2. 素早い解決方法:型アサーション
クイックプロトタイプや内部スクリプトを作成していて、何がスローされるか正確に分かっている場合があります。そのような場合は、型アサーションを使用して手続きを省略できます。ただし、注意が必要です。エラーが想定と異なる場合、実行時にerr.messageは警告なしにundefinedを返します。
try {
// ロジック
} catch (error) {
const err = error as Error;
console.log(err.message);
}
3. AxiosやAPIエラーの処理
AxiosのようなAPIクライアントは、独自の絞り込みツールを提供しています。データを取得する場合、エラーにはステータスコード(404や500など)を含むresponseオブジェクトが含まれていることがあります。ライブラリに組み込まれたガードを使用することで、最善の結果が得られます。
import axios from 'axios';
try {
await axios.get('/api/users/1');
} catch (error) {
if (axios.isAxiosError(error)) {
// 安全に特定のAxiosプロパティにアクセスする
console.error(`Status: ${error.response?.status} - ${error.message}`);
} else {
console.error("Native error:", error);
}
}
4. 最もクリーンな方法:再利用可能なヘルパー
すべてのcatchブロックにif (error instanceof Error)と書くのは、すぐに面倒になります。このロジックをユーティリティ関数に移動することをお勧めします。これにより、catchブロックを簡潔に保ち、コードベース全体で一貫したエラーログ出力を実現できます。
function ensureErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
return "An unknown error occurred";
}
// 使用例:
try {
throw new Error("Database timeout after 5000ms");
} catch (error) {
console.error(ensureErrorMessage(error));
}
なぜTypeScriptはこのように変更されたのか?
以前は、catch (error)のデフォルトはanyでした。これは危険でした。error.something()を呼び出すことができ、コンパイラはそれを許容してしまいますが、本番環境でTypeErrorが発生してアプリがクラッシュする原因になっていました。
useUnknownInCatchVariablesフラグは、catchブロックは何が起こるかわからない場所であることを認識させるためのものです。今は手間が増えたように感じるかもしれませんが、大規模なJavaScriptアプリを悩ませる「Cannot read property of undefined」バグを防ぐことができます。
修正の確認方法
- エディタを確認する:
instanceofチェックで囲むと、error.messageの下にある赤い波線が消えるはずです。 - ビルドを実行する: ターミナルで
npx tsc --noEmitを実行します。修正が正しければ、出力にTS18046エラーは表示されなくなります。 - エッジケースをテストする: 手動で文字列をスロー(
throw "API Key Missing")してみて、elseロジックがクラッシュせずに処理できるか確認してください。
エラー管理のプロのヒント
- 'any'の誘惑に負けない:
catch (error: any)を使用しないでください。これは、TypeScriptを使用する目的である安全機能をバイパスする近道にすぎません。 - コンテキストを保持する: エラーを絞り込む際、元の
unknownオブジェクトをSentryなどのサービスにログ出力してください。デバッグに役立つ隠れたメタデータが含まれていることがよくあります。 - 設定: この挙動によって大規模なレガシープロジェクトが壊れてしまう場合は、
tsconfig.jsonで"useUnknownInCatchVariables": falseを設定することで一時的に無効化できます。ただし、長期的には型を修正する方が常に良い選択です。

