TypeScriptで開発をしていると、ある日突然 'X' is of type 'unknown' というエラーに遭遇することがあります。このエラーの正式名称は TS18046 です。
TS18046は、unknown型の値をそのまま使おうとしたときに発生するエラーです。TypeScript 4.4以降で useUnknownInCatchVariables が strict モードに含まれるようになったことで、catch句の変数が自動的にunknown型になり、このエラーに遭遇する開発者が急増しています。
unknown型は「型安全なany」とも呼ばれ、外部から入ってくるデータを安全に扱うためのTypeScriptの重要な仕組みです。しかし、unknown型の値は型を絞り込むまで一切の操作が許可されないため、正しい扱い方を知らないとコード全体が動かなくなります。
この記事では、TS18046エラーが発生する全パターンを網羅し、それぞれについてエラーメッセージ・NGコード・原因の解説・OKコード(修正例)を掲載します。try-catch、JSON.parse、APIレスポンス、ジェネリクス、React、バリデーションライブラリまで、実務で遭遇するあらゆるケースを完全にカバーします。
この記事で学べること
- TS18046エラーメッセージの読み方と意味
- unknown型とは何か / any型との違い
- try-catchでcatch変数がunknownになるケースと解決方法
- JSON.parseの戻り値を型安全に扱うパターン
- fetch API / axiosのレスポンスを安全に扱う方法
- ジェネリクスとunknownの関係
- typeof / instanceof / in / ユーザー定義型ガードによる型絞り込み完全ガイド
- unknown を安全に扱うユーティリティ関数集
- Zod / valibot / io-tsなどバリデーションライブラリとの連携
- ReactでのTS18046(ErrorBoundary・useReducer等)
- tsconfig.jsonの関連設定と段階的移行戦略
TS18046エラーとは?
TS18046は、TypeScriptコンパイラがunknown型の値に対して操作を行おうとしたときに発生するエラーです。正式なエラーメッセージは次のとおりです。
error TS18046: ‘X’ is of type ‘unknown’.
日本語に訳すと「‘X’ は unknown 型です」となります。つまり、ある変数が unknown 型であるにもかかわらず、型を絞り込まないまま直接使おうとしたときに発生するエラーです。
エラーメッセージの読み方
TS18046のエラーメッセージは、一定のフォーマットに従っています。具体例で構造を確認しましょう。
エラーメッセージの例
src/index.ts:5:15 - error TS18046: 'e' is of type 'unknown'.
5 console.log(e.message);
~
このエラーメッセージは以下のパーツで構成されています。
| パーツ |
例 |
意味 |
| ファイルパス |
src/index.ts |
エラーが発生したファイル |
| 行:列 |
5:15 |
エラー箇所(5行目の15文字目) |
| エラーコード |
TS18046 |
TypeScriptのエラー番号 |
| ‘X’ |
e |
unknown型である変数名 |
| type ‘unknown’ |
unknown |
現在の型(常にunknown) |
覚え方:「’変数名‘ is of type ‘unknown’」と表示されたら、「この変数はunknown型なので、型を絞り込んでから使ってください」というTypeScriptからのメッセージです。
unknown型とは何か
unknown 型は TypeScript 3.0 で導入された特殊な型で、「何の型かわからない値」を表現します。any 型と似ていますが、決定的な違いがあります。
| 特性 |
any |
unknown |
| 何でも代入できる |
はい |
はい |
| 他の型に代入できる |
はい(危険) |
いいえ(安全) |
| プロパティアクセス |
可能(型チェックなし) |
不可(型を絞り込む必要あり) |
| メソッド呼び出し |
可能(型チェックなし) |
不可(型を絞り込む必要あり) |
| 算術演算 |
可能 |
不可 |
| 型安全性 |
なし(型チェック無効化) |
あり(型チェック強制) |
要約すると、unknownは「何でも入れられるけど、中身を確認するまで使えない箱」です。一方、anyは「何でも入れられて、確認なしで使える箱」ですが、型安全性が完全に失われます。
any型 vs unknown型の違い(コード比較)
コードで具体的な違いを見てみましょう。
any型 – 何でもできる(危険)
let value: any = "hello";
// any型は何でもできてしまう(型チェックが無効化される)
value.foo.bar; // コンパイルOK(実行時エラー!)
value.trim(); // コンパイルOK
value.toFixed(2); // コンパイルOK(実行時エラー!)
value(); // コンパイルOK(実行時エラー!)
const num: number = value; // コンパイルOK(型が間違っている!)
unknown型 – 型を絞り込むまで使えない(安全)
let value: unknown = "hello";
// unknown型は型を絞り込まないと何もできない
value.foo.bar; // TS18046 エラー
value.trim(); // TS18046 エラー
value.toFixed(2); // TS18046 エラー
value(); // TS18046 エラー
const num: number = value; // TS2322 エラー
// typeof で型を絞り込めば使える
if (typeof value === "string") {
value.trim(); // OK - string型に絞り込まれた
}
ポイント:unknown型は「型安全なany」です。anyはTypeScriptの型チェックを完全に無効化しますが、unknownは型チェックを強制します。新しいコードではanyの代わりにunknownを使うのがベストプラクティスです。
なぜunknownを使うべきなのか(型安全性のメリット)
any型を使うと、TypeScriptの型チェックが完全に無効化されます。これは便利に見えますが、実行時エラーの原因になります。
any型による実行時エラーの例
function processData(data: any) {
// TypeScriptはエラーを出さないが、実行時にクラッシュする可能性
const name = data.user.name.toUpperCase(); // data が null なら?
const age = data.user.age.toFixed(0); // age が string なら?
return { name, age };
}
// コンパイルは通るが、実行時に TypeError: Cannot read properties of null
processData(null);
unknown型なら安全(コンパイル時にエラーを検出)
function processData(data: unknown) {
// unknown型なので、型を絞り込まないとアクセスできない
// data.user.name.toUpperCase(); // TS18046 エラー!
// 型ガードで安全にアクセス
if (
typeof data === "object" &&
data !== null &&
"user" in data &&
typeof data.user === "object" &&
data.user !== null &&
"name" in data.user &&
typeof data.user.name === "string"
) {
const name = data.user.name.toUpperCase(); // OK - 安全
return name;
}
return null;
}
unknown型のメリット:コードを書く段階では面倒に感じるかもしれませんが、unknownを使うことで実行時エラーをコンパイル時に検出できます。特にAPIレスポンスやユーザー入力など、外部から入ってくるデータには必ずunknownを使いましょう。
TypeScript 4.4以降でTS18046が増えた理由
TypeScript 4.4で useUnknownInCatchVariables オプションが導入され、strict モードに含まれるようになりました。これにより、catch句の変数がデフォルトでunknown型になります。
TypeScript 4.3 以前(catch の e は any)
try {
riskyOperation();
} catch (e) {
// TypeScript 4.3以前: e は any型(型チェックなし)
console.log(e.message); // OK(でも安全ではない)
}
TypeScript 4.4 以降 + strict(catch の e は unknown)
try {
riskyOperation();
} catch (e) {
// TypeScript 4.4以降 + strict: e は unknown型
console.log(e.message); // TS18046 エラー!
}
この変更の理由は、JavaScriptではthrow文で任意の値を投げられるからです。
throwで投げられるのはErrorだけではない
// JavaScript では何でも throw できる
throw new Error("エラーメッセージ"); // Error オブジェクト
throw "文字列エラー"; // 文字列
throw 42; // 数値
throw { code: 404, msg: "Not Found" }; // オブジェクト
throw null; // null
throw undefined; // undefined
// catch で e.message にアクセスしても安全とは限らない
try {
throw "文字列エラー";
} catch (e) {
// e.message → TypeError: e.message is not a function
// なぜなら e は string であり、message プロパティを持たない
}
注意:TypeScript 4.4以降で strict: true を使っているプロジェクトでは、useUnknownInCatchVariables が自動的に有効になります。既存プロジェクトをアップグレードした際に、大量のTS18046エラーが出ることがあります。
TS18046が発生する主なケース一覧
TS18046エラーが発生する代表的なケースをまとめます。
| ケース |
説明 |
解決方法 |
| try-catch の catch 変数 |
strict モードで e が unknown に |
instanceof Error で判定 |
| JSON.parse の戻り値 |
パース結果が any/unknown |
型ガード / Zod で検証 |
| fetch APIレスポンス |
response.json() が unknown |
型ガード / バリデーション |
| unknown 型引数 |
ジェネリクスで unknown が渡される |
型制約 / 型ガード |
| 明示的 unknown 宣言 |
let x: unknown = … で宣言 |
typeof / instanceof |
| Record<string, unknown> |
値が unknown のオブジェクト |
in演算子 + typeof |
| 外部ライブラリの戻り値 |
ライブラリが unknown を返す |
ドキュメント確認 + 型ガード |
以降のセクションでは、これらのケースを一つずつ詳しく見ていきます。
基本的なケースと解決方法
TS18046エラーの基本的なパターンと、その解決方法を見ていきましょう。unknown型の値を使うには、型の絞り込み(Type Narrowing)を行う必要があります。
unknown型の変数を直接使おうとした場合
最もシンプルなケースです。unknown型の変数に対して、プロパティアクセスやメソッド呼び出しを行うとTS18046が発生します。
error TS18046: ‘value’ is of type ‘unknown’.
NGコード
function handleValue(value: unknown) {
// unknown型に対するあらゆる操作がTS18046エラーになる
console.log(value.toString()); // TS18046 エラー
console.log(value.length); // TS18046 エラー
console.log(value + 1); // TS18046 エラー
value(); // TS18046 エラー
}
原因:unknown型は型安全性を保証するために、型を絞り込まない限り一切の操作を許可しません。ただし、typeof・instanceof・等値比較(===)・論理演算子・型アサーション(as)は使用可能です。
typeof による型の絞り込み
typeof 演算子を使うことで、unknown型の値を特定のプリミティブ型に絞り込めます。最も基本的で安全な方法です。
error TS18046: ‘value’ is of type ‘unknown’.
NGコード
function printLength(value: unknown) {
console.log(value.length); // TS18046 エラー
}
OKコード(typeof で絞り込み)
function printLength(value: unknown) {
if (typeof value === "string") {
console.log(value.length); // OK - string型に絞り込まれた
console.log(value.trim()); // OK - string のメソッド
}
}
typeof で判定できる型の一覧です。
| typeof の結果 |
TypeScript上の型 |
使用例 |
"string" |
string |
value.trim() |
"number" |
number |
value.toFixed(2) |
"boolean" |
boolean |
value ? "yes" : "no" |
"bigint" |
bigint |
value + 1n |
"symbol" |
symbol |
value.description |
"function" |
Function |
value() |
"object" |
object | null |
nullチェック別途必要 |
"undefined" |
undefined |
値が存在しない |
typeofによる複数型の絞り込み
function formatValue(value: unknown): string {
if (typeof value === "string") return value.trim();
if (typeof value === "number") return value.toFixed(2);
if (typeof value === "boolean") return value ? "true" : "false";
if (typeof value === "bigint") return value.toString();
return String(value);
}
注意:typeof null === "object" です。"object" と判定された場合、必ず value !== null のチェックも行いましょう。
instanceof による型の絞り込み
instanceof 演算子を使うと、unknown型の値が特定のクラスのインスタンスかどうかを判定できます。
OKコード(instanceof で絞り込み)
function processUnknown(value: unknown) {
if (value instanceof Error) console.log(value.message); // Error型
if (value instanceof Date) console.log(value.toISOString()); // Date型
if (value instanceof RegExp) console.log(value.source); // RegExp型
if (value instanceof Map) console.log(value.size); // Map型
if (value instanceof Set) console.log(value.size); // Set型
if (value instanceof Promise) value.then(v => console.log(v)); // Promise型
}
型アサーション(as)での解決
型アサーション(as)を使えば、unknown型を特定の型として扱えます。ただし型の安全性は開発者の責任です。
型アサーション vs 型ガード
// 型アサーション(安全性は自己責任)
function getLength(data: unknown): number {
return (data as string).length; // dataがstringでなければ実行時エラー
}
// 型ガードと組み合わせて安全に(推奨)
function getLengthSafe(data: unknown): number | null {
if (typeof data === "string") return data.length;
if (Array.isArray(data)) return data.length;
return null;
}
注意:型アサーション(as)は型チェックを上書きします。可能な限り型ガードを使いましょう。
型ガード関数(ユーザー定義型ガード)での解決
ユーザー定義型ガードは、value is Type という戻り値型を持つ関数で、複雑な型チェックを再利用可能にカプセル化できます。
ユーザー定義型ガードの例
interface User {
name: string;
age: number;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof (value as User).name === "string" &&
"age" in value &&
typeof (value as User).age === "number"
);
}
function greetUser(user: unknown) {
if (isUser(user)) {
console.log(`Hello, ${user.name}!`); // OK
console.log(`Age: ${user.age}`); // OK
}
}
assertsキーワードによる型ガード
asserts キーワードを使った型ガードは、条件を満たさない場合にエラーをスローし、以降のコードで型が絞り込まれることを保証します。
asserts型ガードの例
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error(`Expected string, got ${typeof value}`);
}
}
function processInput(input: unknown) {
assertIsString(input);
// この行以降、input は string 型
console.log(input.trim()); // OK
console.log(input.length); // OK
}
is vs asserts の違い:value is Type はif文の中で型を絞り込みます。asserts value is Type は関数が正常に返れば以降のすべてのコードで型が絞り込まれます。
解決方法の比較まとめ
| 方法 |
安全性 |
適用場面 |
注意点 |
| typeof |
高 |
プリミティブ型 |
objectはnull含む |
| instanceof |
高 |
クラスインスタンス |
プレーンオブジェクト不可 |
| 型ガード関数 |
高 |
複雑な型の判定 |
正確な実装が必要 |
| asserts型ガード |
高 |
前提条件の検証 |
例外をスロー |
| 型アサーション(as) |
低 |
型が確実なとき |
実行時エラーのリスク |
| バリデーションライブラリ |
最高 |
外部データの検証 |
追加依存が必要 |
try-catch の catch でのTS18046
TS18046エラーに遭遇する最も多いケースが、try-catch文のcatch句です。TypeScript 4.4以降、strictモードでは catch の変数が自動的に unknown 型になります。
catch(e) の e が unknown になる理由
JavaScriptでは throw であらゆる値を投げることができます。Errorオブジェクトだけでなく、文字列、数値、null、undefined、さらにはオブジェクトも投げられます。そのため、catch句の変数の型を Error と仮定するのは安全ではありません。
error TS18046: ‘e’ is of type ‘unknown’.
NGコード(strictモードで発生)
try {
JSON.parse("invalid json");
} catch (e) {
// strict モードでは e は unknown 型
console.log(e.message); // TS18046 エラー!
console.log(e.stack); // TS18046 エラー!
console.log(e.name); // TS18046 エラー!
}
解決方法1: instanceof Error でチェック(推奨)
最も安全で推奨される方法は、instanceof Error で型を絞り込むことです。
OKコード(instanceof Error)
try {
JSON.parse("invalid json");
} catch (e) {
if (e instanceof Error) {
console.log(e.message); // OK - "Unexpected token i..."
console.log(e.stack); // OK - スタックトレース
console.log(e.name); // OK - "SyntaxError"
} else {
// Error以外が投げられた場合のフォールバック
console.log("Unknown error:", e);
}
}
解決方法2: カスタムエラークラスの判定
アプリケーション固有のカスタムエラークラスを使っている場合、instanceof でより具体的に型を絞り込めます。
カスタムエラークラスの判定
// カスタムエラークラスの定義
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public endpoint: string
) {
super(message);
this.name = "ApiError";
}
}
class ValidationError extends Error {
constructor(
message: string,
public field: string
) {
super(message);
this.name = "ValidationError";
}
}
// catch で型を絞り込む
try {
await fetchUserData();
} catch (e) {
if (e instanceof ApiError) {
console.log(e.statusCode); // OK - ApiError固有プロパティ
console.log(e.endpoint); // OK
} else if (e instanceof ValidationError) {
console.log(e.field); // OK - ValidationError固有プロパティ
} else if (e instanceof Error) {
console.log(e.message); // OK - 一般的なError
} else {
console.log("Unknown error:", e);
}
}
解決方法3: エラーメッセージの安全な取得パターン
Error以外の値が投げられる可能性も考慮した、堅牢なエラーメッセージ取得パターンです。
安全なエラーメッセージ取得
// パターン1: シンプルな取得
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
}
// パターン2: スタックトレース付き
function getErrorDetail(error: unknown): { message: string; stack?: string } {
if (error instanceof Error) {
return { message: error.message, stack: error.stack };
}
return { message: String(error) };
}
// 使用例
try {
riskyOperation();
} catch (e) {
const msg = getErrorMessage(e);
console.error(`操作に失敗しました: ${msg}`);
}
解決方法4: isError型ガードの自作
プロジェクト全体で再利用できる isError 型ガード関数と、unknown を Error に変換する toError ユーティリティを作成します。
isError / toError ユーティリティ
// 型ガード: unknown が Error かどうか
function isError(value: unknown): value is Error {
return value instanceof Error;
}
// unknown を Error に変換(Error でなければラップ)
function toError(value: unknown): Error {
if (value instanceof Error) return value;
if (typeof value === "string") return new Error(value);
if (
typeof value === "object" &&
value !== null &&
"message" in value &&
typeof (value as { message: unknown }).message === "string"
) {
return new Error((value as { message: string }).message);
}
return new Error(String(value));
}
// 使用例
try {
riskyOperation();
} catch (e) {
const error = toError(e);
console.error(error.message); // OK - 常に Error 型
}
解決方法5: catch(e: any) で回避(非推奨)
catch句で e に明示的に any 型を付けることでTS18046を回避できます。ただし、型安全性が失われるため非推奨です。
非推奨:catch(e: any)
// 非推奨:any で回避
try {
riskyOperation();
} catch (e: any) {
console.log(e.message); // OK(でも型安全ではない)
}
// 推奨:instanceof でチェック
try {
riskyOperation();
} catch (e) {
if (e instanceof Error) {
console.log(e.message); // OK(型安全)
}
}
注意:catch(e: any) は型チェックを無効化するため、TypeScriptを使う意味が薄れます。instanceof Error で型を絞り込むか、toError ユーティリティを使いましょう。
実務でよく使うcatchパターン集
実務で頻繁に使うcatchパターンをまとめます。
実務パターン集
// パターン1: APIリクエスト
async function fetchData() {
try {
const res = await fetch("/api/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
const message = e instanceof Error ? e.message : "Unknown error";
console.error(`API Error: ${message}`);
throw e; // 再スロー
}
}
// パターン2: ファイル読み込み(Node.js)
try {
const data = await fs.readFile("config.json", "utf-8");
} catch (e) {
if (e instanceof Error && "code" in e) {
const nodeErr = e as NodeJS.ErrnoException;
if (nodeErr.code === "ENOENT") {
console.log("File not found");
}
}
}
// パターン3: ログ送信(エラーを握りつぶさない)
try {
processOrder(order);
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
logToSentry(error); // Error型を要求するログサービス
throw error;
}
ポイント:catch句でのTS18046解決は、instanceof Error が最もシンプルで安全です。プロジェクト全体で統一するために、getErrorMessage や toError のようなユーティリティ関数を作成しておくと効率的です。
JSON.parse でのTS18046
JSON.parse() の戻り値は any 型ですが、strict設定やプロジェクトのルールによって unknown として扱うべきケースがあります。外部から受け取ったJSON文字列をパースした結果を安全に使う方法を解説します。
JSON.parseの戻り値を安全に扱う
JSON.parse() の戻り値は TypeScript の型定義上は any ですが、実際にはどんな値が入っているかわかりません。unknown として扱い、型を検証してから使うのがベストプラクティスです。
error TS18046: ‘parsed’ is of type ‘unknown’.
NGコード(unknownとして受け取った場合)
const jsonStr = '{"name": "Alice", "age": 30}';
const parsed: unknown = JSON.parse(jsonStr);
console.log(parsed.name); // TS18046 エラー
console.log(parsed.age); // TS18046 エラー
OKコード(型ガードで検証)
interface UserData {
name: string;
age: number;
}
function isUserData(value: unknown): value is UserData {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof (value as UserData).name === "string" &&
"age" in value &&
typeof (value as UserData).age === "number"
);
}
const parsed: unknown = JSON.parse(jsonStr);
if (isUserData(parsed)) {
console.log(parsed.name); // OK - "Alice"
console.log(parsed.age); // OK - 30
}
Zod によるバリデーション(推奨)
手動で型ガードを書くのは面倒で、複雑な型では間違いやすくなります。Zod を使えば、スキーマ定義と型推論を同時に行えます。
Zod によるJSON.parseの検証
import { z } from "zod";
// スキーマ定義(型定義と検証ロジックを同時に定義)
const UserSchema = z.object({
name: z.string(),
age: z.number().int().positive(),
});
// 型はスキーマから自動推論される
type User = z.infer<typeof UserSchema>;
// => { name: string; age: number }
// 安全にパース
const jsonStr = '{"name": "Alice", "age": 30}';
const result = UserSchema.safeParse(JSON.parse(jsonStr));
if (result.success) {
const user = result.data; // 型: User
console.log(user.name); // OK - "Alice"
console.log(user.age); // OK - 30
} else {
console.error(result.error.issues);
}
safeParse ユーティリティの自作
バリデーションライブラリを使わない場合の、安全なJSONパースユーティリティです。
safeParse ユーティリティ
type ParseResult<T> =
| { success: true; data: T }
| { success: false; error: Error };
function safeParse<T>(
json: string,
guard: (value: unknown) => value is T
): ParseResult<T> {
try {
const parsed: unknown = JSON.parse(json);
if (guard(parsed)) {
return { success: true, data: parsed };
}
return { success: false, error: new Error("Validation failed") };
} catch (e) {
return { success: false, error: e instanceof Error ? e : new Error(String(e)) };
}
}
// 使用例
const result = safeParse(jsonStr, isUserData);
if (result.success) {
console.log(result.data.name); // OK - 型安全
}
ポイント:JSON.parseの戻り値は実質的にunknownです。型アサーション(as)で済ませがちですが、外部データに対しては必ずバリデーションを行いましょう。Zodを使うのが最も簡潔で安全です。
API レスポンス・外部データでのTS18046
外部から取得するデータは、型が保証されていません。fetch API、axios、WebSocket、localStorage など、外部データソースからの値を安全に扱う方法を解説します。
fetch APIレスポンスの安全な型付け
response.json() の戻り値は Promise<any> ですが、unknownとして扱うべきです。
fetch APIの安全な型付け
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json: unknown = await res.json();
return UserSchema.parse(json); // バリデーション + 型付け
}
axiosレスポンスの安全な型付け
axiosでは response.data がデフォルトで any です。ジェネリクスで型を指定できますが、実際の検証は行われません。
axiosの安全な型付け
import axios from "axios";
import { z } from "zod";
// NGパターン: ジェネリクスだけでは実際の検証が行われない
const res = await axios.get<User>("/api/user");
// res.data は User型だが、実際のデータが正しい保証はない
// OKパターン: Zodで検証
async function fetchUserSafe(id: number): Promise<User> {
const res = await axios.get(`/api/users/${id}`);
return UserSchema.parse(res.data); // 実際にデータを検証
}
localStorage / sessionStorage の値
localStorage.getItem() は string | null を返します。JSON.parse と組み合わせる場合、unknown として安全に扱う必要があります。
localStorageの安全な読み取り
function getFromStorage<T>(
key: string,
guard: (value: unknown) => value is T
): T | null {
const raw = localStorage.getItem(key);
if (raw === null) return null;
try {
const parsed: unknown = JSON.parse(raw);
return guard(parsed) ? parsed : null;
} catch {
return null;
}
}
// 使用例
const settings = getFromStorage("settings", isSettings);
WebSocket メッセージの安全な型付け
WebSocketメッセージの型付け
const MessageSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("chat"), text: z.string(), userId: z.string() }),
z.object({ type: z.literal("join"), userId: z.string() }),
z.object({ type: z.literal("leave"), userId: z.string() }),
]);
ws.addEventListener("message", (event) => {
const parsed = MessageSchema.safeParse(JSON.parse(event.data));
if (!parsed.success) return;
const msg = parsed.data;
switch (msg.type) {
case "chat": console.log(msg.text); break;
case "join": console.log(msg.userId); break;
case "leave": console.log(msg.userId); break;
}
});
外部データの原則:fetch・axios・WebSocket・localStorage・URLSearchParams・環境変数など、外部から入ってくるデータはすべてunknownとして扱うべきです。型アサーション(as)で済ませるのはアンチパターンです。バリデーションを通してから使いましょう。
ジェネリクスと unknown のTS18046
ジェネリクス関数の型引数がunknownに推論されたり、Record<string, unknown> のような型を使うとTS18046が発生することがあります。
ジェネリクス関数の型引数がunknown
型引数を明示的に指定しない場合、TypeScriptは引数からジェネリクスの型を推論します。推論できない場合は unknown になります。
error TS18046: ‘item’ is of type ‘unknown’.
ジェネリクスとunknown
// T に制約がないため、unknown として扱われるケースがある
function processItem<T>(item: T) {
// T に制約がないため、プロパティアクセスは不可
console.log(item.name); // エラー: T にnameプロパティがない
}
// 解決: extends で型制約を追加
function processItem<T extends { name: string }>(item: T) {
console.log(item.name); // OK - 制約によりnameが保証
}
Record<string, unknown> の扱い
Record<string, unknown> は「キーがstring、値がunknownのオブジェクト」を表します。値にアクセスすると unknown 型が返るため、型ガードが必要です。
error TS18046: ‘value’ is of type ‘unknown’.
Record<string, unknown>の操作
const config: Record<string, unknown> = {
host: "localhost",
port: 3000,
debug: true,
};
// NGコード
const host = config.host.toUpperCase(); // TS18046 エラー
// OKコード: typeofで絞り込み
const hostValue = config.host;
if (typeof hostValue === "string") {
console.log(hostValue.toUpperCase()); // OK
}
// ヘルパー関数で安全に取得
function getString(obj: Record<string, unknown>, key: string): string | undefined {
const val = obj[key];
return typeof val === "string" ? val : undefined;
}
function getNumber(obj: Record<string, unknown>, key: string): number | undefined {
const val = obj[key];
return typeof val === "number" ? val : undefined;
}
const host = getString(config, "host"); // string | undefined
const port = getNumber(config, "port"); // number | undefined
unknown[] 配列の操作
unknown[] の各要素を操作するには、要素ごとに型を絞り込む必要があります。
unknown[]の安全な操作
const items: unknown[] = ["hello", 42, true, { name: "Alice" }];
// 文字列だけを抽出
const strings = items.filter(
(item): item is string => typeof item === "string"
); // 型: string[]
// 数値だけを抽出して合計
const numbers = items.filter(
(item): item is number => typeof item === "number"
); // 型: number[]
const sum = numbers.reduce((a, b) => a + b, 0); // OK
// map で各要素を安全に変換
const labels = items.map((item) => {
if (typeof item === "string") return `str: ${item}`;
if (typeof item === "number") return `num: ${item}`;
return `other: ${String(item)}`;
});
ポイント:Array.filter でユーザー定義型ガードを使えば、unknown[] から特定の型の要素だけを型安全に抽出できます。コールバックの戻り値型に item is Type を指定するのがコツです。
型の絞り込みテクニック(完全版)
TS18046を解決するために使える型の絞り込み(Type Narrowing)テクニックをすべてまとめます。
typeof ガード
typeof 演算子はプリミティブ型の判定に使います。前セクションで紹介した通り、"string"、"number"、"boolean"、"bigint"、"symbol"、"function"、"object"、"undefined" の8種類を判定できます。
typeof ガードの完全パターン
function stringify(value: unknown): string {
switch (typeof value) {
case "string": return value; // string
case "number": return value.toString(); // number
case "boolean": return value ? "true" : "false"; // boolean
case "bigint": return value.toString(); // bigint
case "symbol": return value.toString(); // symbol
case "function": return "[Function]"; // Function
case "object": return JSON.stringify(value); // object | null
case "undefined": return "undefined"; // undefined
}
}
in 演算子ガード
in 演算子を使うと、オブジェクトに特定のプロパティが存在するかを判定できます。Discriminated Union パターンと組み合わせると強力です。
in 演算子ガード
function handleResponse(value: unknown) {
// まず object かつ non-null であることを確認
if (typeof value !== "object" || value === null) return;
// in 演算子でプロパティの存在をチェック
if ("data" in value) {
// value は { data: unknown } と推論される
console.log(value.data); // OK(ただし data は unknown)
}
if ("error" in value && "message" in value) {
console.log(value.error, value.message); // OK
}
}
等値チェック(=== / !==)
厳密等値演算子を使って、特定の値と比較することで型を絞り込めます。
等値チェックによる絞り込み
function handleValue(value: unknown) {
// null / undefined の除外
if (value === null) return "null";
if (value === undefined) return "undefined";
// 特定のリテラル値と比較
if (value === true) return "boolean true";
if (value === 0) return "zero";
if (value === "") return "empty string";
return String(value);
}
Array.isArray() による配列判定
typeof では配列を判定できません(typeof [] === "object")。Array.isArray() を使うことで、unknown型から安全に配列を抽出できます。
Array.isArray()による絞り込み
function processData(data: unknown) {
if (Array.isArray(data)) {
// data は any[] に絞り込まれる
console.log(data.length); // OK
data.forEach(item => { // OK
console.log(item); // item は any
});
}
}
// 型付き配列かどうかチェックする型ガード
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
Discriminated Union パターン
共通の判別プロパティ(discriminant)を持つユニオン型を使うことで、型を安全に絞り込めます。
Discriminated Unionパターン
type ApiResult =
| { status: "success"; data: User }
| { status: "error"; message: string; code: number };
// status プロパティで型を判別
function handleResult(result: ApiResult) {
switch (result.status) {
case "success":
console.log(result.data.name); // OK - User型
break;
case "error":
console.log(result.message); // OK - string型
console.log(result.code); // OK - number型
break;
}
}
// unknown から Discriminated Union に変換する型ガード
function isApiResult(value: unknown): value is ApiResult {
if (typeof value !== "object" || value === null) return false;
if (!("status" in value)) return false;
const s = (value as { status: unknown }).status;
return s === "success" || s === "error";
}
unknown を安全に扱うユーティリティ関数集
プロジェクト全体で再利用できるユーティリティ関数を紹介します。これらを utils/typeGuards.ts のようなファイルにまとめておくと、unknown型の値を効率的に扱えます。
プリミティブ型ガード
isString / isNumber / isBoolean
export function isString(value: unknown): value is string {
return typeof value === "string";
}
export function isNumber(value: unknown): value is number {
return typeof value === "number" && !Number.isNaN(value);
}
export function isBoolean(value: unknown): value is boolean {
return typeof value === "boolean";
}
export function isNonNullObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
isError / toError
isError / toError / getErrorMessage
export function isError(value: unknown): value is Error {
return value instanceof Error;
}
export function toError(value: unknown): Error {
if (value instanceof Error) return value;
if (typeof value === "string") return new Error(value);
return new Error(String(value));
}
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
}
hasProperty(プロパティ存在チェック)
hasProperty ユーティリティ
// 特定のプロパティが存在し、指定した型であることをチェック
export function hasProperty<K extends string>(
obj: unknown,
key: K
): obj is Record<K, unknown> {
return typeof obj === "object" && obj !== null && key in obj;
}
// 使用例
function process(data: unknown) {
if (hasProperty(data, "name")) {
// data は Record<"name", unknown> 型
const name = data.name; // unknown
if (typeof name === "string") {
console.log(name.toUpperCase()); // OK
}
}
}
narrowType(汎用型絞り込み)
narrowType ユーティリティ
// Result型(成功 or 失敗)
type NarrowResult<T> =
| { ok: true; value: T }
| { ok: false; error: string };
// 型ガード関数を渡して unknown を安全に変換
export function narrowType<T>(
value: unknown,
guard: (v: unknown) => v is T,
errorMsg = "Type guard failed"
): NarrowResult<T> {
if (guard(value)) {
return { ok: true, value };
}
return { ok: false, error: errorMsg };
}
// 使用例
const result = narrowType(data, isUser, "Invalid user data");
if (result.ok) {
console.log(result.value.name); // OK - User型
}
まとめ:これらのユーティリティ関数を utils/typeGuards.ts にまとめておくと、プロジェクト全体でunknown型の値を統一的に扱えます。チーム開発では特に効果的です。
バリデーションライブラリとの連携
unknown型の値を手動で検証するのは面倒でエラーが起きやすいです。バリデーションライブラリを使えば、スキーマ定義と型推論を同時に行えます。主要なライブラリを比較します。
Zod(最も人気)
Zodは TypeScript ファーストのバリデーションライブラリで、最も広く使われています。スキーマから型が自動推論されるため、型定義の重複がありません。
Zod の基本パターン
import { z } from "zod";
// スキーマ定義
const UserSchema = z.object({
id: z.number(),
name: z.string().min(1),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
createdAt: z.string().datetime(),
});
// 型を自動推論
type User = z.infer<typeof UserSchema>;
// parse: 失敗時はエラーをスロー
const user = UserSchema.parse(unknownData); // User型 or ZodError
// safeParse: 成功/失敗を Result で返す
const result = UserSchema.safeParse(unknownData);
if (result.success) {
console.log(result.data.name); // OK - User型
} else {
console.error(result.error.flatten());
}
Valibot(軽量代替)
Valibotは Zod と似たAPIですが、バンドルサイズが非常に小さいのが特徴です。Tree-shakingに最適化されています。
Valibot の基本パターン
import * as v from "valibot";
const UserSchema = v.object({
id: v.number(),
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
});
type User = v.InferOutput<typeof UserSchema>;
const result = v.safeParse(UserSchema, unknownData);
if (result.success) {
console.log(result.output.name); // OK
}
ライブラリ比較表
| ライブラリ |
バンドルサイズ |
型推論 |
特徴 |
| Zod |
~13KB (gzip) |
自動推論 |
最も人気、豊富なAPI |
| Valibot |
~1KB (gzip) |
自動推論 |
超軽量、Tree-shaking対応 |
| io-ts |
~5KB (gzip) |
自動推論 |
fp-ts連携、関数型スタイル |
| Yup |
~15KB (gzip) |
InferType |
フォームバリデーション向け |
| 自作型ガード |
0KB |
手動 |
依存なし、小規模向き |
おすすめ:新しいプロジェクトでは Zod がおすすめです。バンドルサイズが気になる場合は Valibot を検討してください。小規模なプロジェクトや外部依存を避けたい場合は自作の型ガードで十分です。
React でのTS18046
React + TypeScriptのプロジェクトでもTS18046に遭遇することがあります。代表的なケースと解決方法を紹介します。
ErrorBoundary の componentDidCatch
クラスコンポーネントの componentDidCatch のエラー引数は Error 型ですが、コンポーネント外でcatchした場合はunknownになることがあります。
ErrorBoundary でのエラーハンドリング
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ error: Error | null }
> {
state = { error: null as Error | null };
static getDerivedStateFromError(error: Error) {
return { error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// error は Error 型(React が保証)
console.error(error.message); // OK
console.error(errorInfo.componentStack); // OK
}
render() {
if (this.state.error) {
return <div>Error: {this.state.error.message}</div>;
}
return this.props.children;
}
}
useReducer の action が unknown
useReducer で型引数を適切に指定しないと、action が unknown になることがあります。
useReducer の型安全な使い方
// Discriminated Union で action を定義
type Action =
| { type: "INCREMENT" }
| { type: "DECREMENT" }
| { type: "SET"; payload: number };
interface State {
count: number;
}
// reducer に明示的に型を指定
function reducer(state: State, action: Action): State {
switch (action.type) {
case "INCREMENT": return { count: state.count + 1 };
case "DECREMENT": return { count: state.count - 1 };
case "SET": return { count: action.payload }; // OK
}
}
// useReducer は reducer 関数から型を自動推論
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "SET", payload: 10 }); // OK - 型安全
Context のデフォルト値
createContext のデフォルト値を null や undefined にすると、Context の値がunknown的に扱われることがあります。
型安全な Context パターン
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
// null を初期値にし、カスタムHookで非null保証
const AuthContext = createContext<AuthContextType | null>(null);
export function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === null) {
throw new Error("useAuth must be used within AuthProvider");
}
return context; // AuthContextType(non-null)
}
サードパーティHookの戻り値
サードパーティライブラリのHookが unknown を返す場合の対処法です。
サードパーティHookのラッパー
import { z } from "zod";
// unknown を返す外部Hook のラッパー
function useTypedQuery<T>(
key: string,
schema: z.ZodType<T>,
fetcher: () => Promise<unknown>
) {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetcher()
.then((raw) => {
const result = schema.safeParse(raw);
if (result.success) setData(result.data);
else setError(new Error("Validation failed"));
})
.catch((e) => setError(e instanceof Error ? e : new Error(String(e))));
}, [key]);
return { data, error };
}
// 使用例
const { data, error } = useTypedQuery("users", UserSchema, fetchUsers);
// data は User | null(型安全)
tsconfig.json の関連設定
TS18046の発生はTypeScriptコンパイラの設定に大きく影響されます。関連する設定項目を解説します。
strict モード
strict: true を設定すると、複数のstrict系オプションが一括で有効になります。TS18046に関連するのは useUnknownInCatchVariables です。
tsconfig.json
{
"compilerOptions": {
// strict: true は以下をすべて有効にする
"strict": true,
// strict に含まれる個別オプション
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitAny": true,
// "noImplicitThis": true,
// "alwaysStrict": true,
// "useUnknownInCatchVariables": true ← TS18046の原因
}
}
useUnknownInCatchVariables
この設定が true のとき、catch句の変数が unknown 型になります。strict: true に含まれるため、strictモードを使っているプロジェクトでは自動的に有効です。
| 設定 |
catch(e) の型 |
TS18046 |
useUnknownInCatchVariables: false |
any |
発生しない |
useUnknownInCatchVariables: true |
unknown |
発生する |
strict: true(暗黙的にtrue) |
unknown |
発生する |
段階的移行戦略
既存プロジェクトで strict: true に移行する際、大量のTS18046エラーが出ることがあります。段階的に移行する方法を紹介します。
段階的移行: tsconfig.json
// ステップ1: strict を有効にし、一時的に useUnknownInCatchVariables を無効化
{
"compilerOptions": {
"strict": true,
"useUnknownInCatchVariables": false // 一時的に無効化
}
}
// ステップ2: catch句を順次修正
// - instanceof Error でチェック
// - getErrorMessage ユーティリティを導入
// ステップ3: すべての catch句を修正したら useUnknownInCatchVariables を削除
{
"compilerOptions": {
"strict": true
// useUnknownInCatchVariables は strict に含まれるので不要
}
}
ポイント:useUnknownInCatchVariables を無効にするのはあくまで一時的な対応です。最終的には有効にして、catch句を正しく型安全に書くことを目指しましょう。tsconfig.jsonの詳細はtsconfig.json 完全ガイドを参照してください。
まとめ
TS18046エラー('X' is of type 'unknown')は、unknown型の値を型を絞り込まずに使おうとしたときに発生します。この記事の内容をまとめます。
TS18046 解決フローチャート
- エラーメッセージを確認 – どの変数が unknown 型か特定する
- unknown の出所を確認 – catch句?JSON.parse?APIレスポンス?ジェネリクス?
- 型の絞り込み方法を選ぶ
- プリミティブ値 →
typeof
- クラスインスタンス →
instanceof
- オブジェクトのプロパティ →
in 演算子
- 複雑な型 → ユーザー定義型ガード(
value is Type)
- 外部データ → バリデーションライブラリ(Zod推奨)
- 型ガードの後でプロパティにアクセス
unknown 型の使いこなしルール
| ルール |
説明 |
| any より unknown を使う |
型安全性を維持するため |
| 外部データは unknown で受ける |
API、JSON.parse、localStorage等 |
| 型ガードで絞り込んでからアクセス |
typeof、instanceof、in、ユーザー定義型ガード |
| catch句では instanceof Error |
e が Error 以外の可能性があるため |
| 型アサーション(as)は最後の手段 |
型の安全性が開発者の責任になる |
| 複雑なデータはZodで検証 |
スキーマ定義と型推論を同時に |
TS18046 vs TS2571 の違い
TS18046と混同しやすいエラーに TS2571(Object is of type 'unknown')があります。
| エラーコード |
メッセージ |
発生条件 |
| TS18046 |
'X' is of type 'unknown' |
unknown型の変数にプロパティアクセス |
| TS2571 |
Object is of type 'unknown' |
unknown型のオブジェクトを操作 |
どちらも解決方法は同じです。型ガード(typeof / instanceof / in / ユーザー定義型ガード)で型を絞り込んでからアクセスしてください。
関連記事
TypeScriptの型システムをさらに深く学びたい方は、以下の記事もあわせてお読みください。