TypeScript を書いていると any・unknown・never という3つの特殊な型に遭遇します。いずれも「通常の型とは異なる特別な役割」を持っており、正しく使い分けることで 型安全なコードと柔軟な設計を両立 できます。
この記事では3つの型の違いを比較したうえで、実務でよく登場するパターンを実例付きで徹底解説します。
1. unknown・any・never を比較する
3つの型はそれぞれ「型システムの中での特別な位置づけ」が異なります。
| 型 | 意味 | 代入できるもの | 使わずに操作できるか | 主な用途 |
|---|---|---|---|---|
any |
型チェックを無効化 | 何でも代入・取り出し可能 | ✅ 可能(危険) | 型がわからないとき・移行期 |
unknown |
型不明(型安全版any) | 何でも代入できるが取り出し前に絞り込み必須 | ❌ 不可(安全) | 外部入力・エラーオブジェクト受け取り |
never |
到達不可能な値 | 何も代入できない | ─(値が存在しない) | 網羅性チェック・throwのみの関数 |
any はすべての型のスーパータイプかつサブタイプという特殊な「脱出口」です。unknown は「すべてのスーパータイプ(トップ型)」で任意の値を受け取れますが操作には絞り込みが必要です。never は「すべてのサブタイプ(ボトム型)」で値が存在しないことを意味します。2. any ── 型チェックを無効にする「緊急脱出口」
2-1. any の基本動作
any 型の変数に対しては、TypeScript の型チェックが完全に無効になります。どんなプロパティへのアクセスも、どんな型への代入も許可されます。
// any はあらゆる型の代わりになれる
let value: any = "hello";
value.toUpperCase(); // OK(型チェックなし)
value.nonExistent(); // OK(実行時エラーになるがコンパイルは通る)
value = 42; // OK
value = { x: 1 }; // OK
const str: string = value; // OK(型チェックなし→実行時まで問題が発覚しない)
const num: number = value; // OK
any を使うと TypeScript の恩恵(型チェック・補完・エラー検出)がすべて失われます。コンパイルは通っても実行時に予期しないエラーが発生する原因になります。any は「一時的な回避策」と位置づけ、長期的には除去することが推奨されます。
2-2. any を使うべき場面・避けるべき場面
| 場面 | 推奨 | 理由 |
|---|---|---|
| JavaScript から TypeScript への段階的移行 | ✅ 一時的に許容 | まだ型が整備されていないコードへの対処 |
| 型定義のないサードパーティライブラリ | ✅ 一時的に許容(@types を先に探す) |
やむを得ない場合 |
| 外部APIのレスポンス | ❌ unknown を使う | 型不明でも型安全に扱える |
| catch ブロックの error 変数 | ❌ unknown を使う | TS4.0以降は unknown が推奨 |
| 通常の変数・関数引数 | ❌ 具体的な型を使う | any を使うと型推論・補完が機能しない |
2-3. any の代替パターン
// NG: any で型情報を消す
function parseConfig(data: any) {
return data.host; // 補完なし・型エラー検出なし
}
// OK: 具体的な型を定義する
interface Config {
host: string;
port: number;
}
function parseConfig(data: Config) {
return data.host; // 補完あり・型安全
}
// OK: 型がわからない場合は unknown を使って絞り込む
function parseConfig(data: unknown): Config {
if (
typeof data === "object" && data !== null &&
"host" in data && "port" in data
) {
return data as Config;
}
throw new Error("Invalid config");
}
3. unknown ── 型安全な「不明な型」
unknown は「どんな値でも受け取れるが、型を確認(絞り込み)しないと使えない」型です。any の型安全版として TypeScript 3.0 で追加されました。
3-1. unknown の基本動作
let value: unknown = "hello";
// NG: 型を確認せずに操作しようとするとコンパイルエラー
// value.toUpperCase(); // Error: Object is of type 'unknown'
// const str: string = value; // Error: Type 'unknown' is not assignable to type 'string'
// OK: typeof で絞り込んでから使う
if (typeof value === "string") {
console.log(value.toUpperCase()); // "HELLO"
}
// OK: any には代入できる(anyはすべてを受け入れる)
let anyValue: any = value; // OK
// NG: 具体的な型へは代入不可(絞り込み必須)
// let str: string = value; // Error
3-2. 型ガードで unknown を絞り込む
function processValue(value: unknown): string {
// typeof による原始型チェック
if (typeof value === "string") {
return value.toUpperCase();
}
if (typeof value === "number") {
return value.toFixed(2);
}
// instanceof によるオブジェクトチェック
if (value instanceof Date) {
return value.toISOString();
}
// in 演算子でプロパティの存在確認
if (typeof value === "object" && value !== null && "name" in value) {
return String((value as { name: unknown }).name);
}
return String(value);
}
型ガードのパターン全般は 型の絞り込み(Type Narrowing)完全ガイド を参照してください。
3-3. JSON.parse の戻り値を unknown で受ける
JSON.parse() の戻り値は TypeScript では any ですが、unknown として受け取ることで安全な処理を強制できます。
// NG: any のまま使うと型安全でない
const data: any = JSON.parse(jsonString);
console.log(data.user.name); // 実行時エラーの可能性あり
// OK: unknown で受け取って型ガードで検証
function safeParseJson(jsonString: string): unknown {
try {
return JSON.parse(jsonString) as unknown;
} catch {
return null;
}
}
interface UserData {
user: { name: string; age: number };
}
function isUserData(value: unknown): value is UserData {
return (
typeof value === "object" && value !== null &&
"user" in value &&
typeof (value as UserData).user === "object"
);
}
const parsed = safeParseJson(jsonString);
if (isUserData(parsed)) {
console.log(parsed.user.name); // 型安全にアクセス
}
3-4. catch ブロックのエラー型(unknown)
TypeScript 4.0 から catch 節の変数 error は unknown 型になりました(useUnknownInCatchVariables: true の場合)。これにより、エラー処理が型安全になりました。
// TS 4.0 以降: catch の error は unknown 型
try {
await fetch("/api/data");
} catch (error: unknown) {
// NG: unknown のまま使うとエラー
// console.log(error.message); // Error: Object is of type 'unknown'
// OK: instanceof で Error かどうか確認
if (error instanceof Error) {
console.error("エラー:", error.message);
console.error("スタック:", error.stack);
} else if (typeof error === "string") {
console.error("エラー文字列:", error);
} else {
console.error("不明なエラー:", String(error));
}
}
// ユーティリティ関数として切り出すと便利
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
return "不明なエラーが発生しました";
}
TS18046「is of type unknown」の解決方法 も合わせて参照してください。
4. never ── 「到達不可能」を表す型
never は「決して発生しない値の型」です。TypeScript が「このコードには絶対に到達しない」と判断したときに現れます。また、明示的に使うことで Union 型の網羅性(exhaustiveness)を保証 するテクニックがあります。
4-1. never が自動的に現れる場面
// 1. 互いに矛盾する型絞り込みの後
function example(value: string | number) {
if (typeof value === "string") {
// value: string
} else if (typeof value === "number") {
// value: number
} else {
// value: never(string でも number でもない値は存在しない)
const impossible: never = value;
}
}
// 2. 空の配列フィルタ後
type StringOrNumber = string | number;
type OnlyString = StringOrNumber extends number ? never : StringOrNumber;
// OnlyString = string(number は never に変換されて除外)
4-2. 網羅性チェック(Exhaustive Check)
switch 文で Union 型のすべてのケースを処理しているかを コンパイル時に検証 できます。
type Shape = "circle" | "square" | "triangle";
function getArea(shape: Shape, size: number): number {
switch (shape) {
case "circle":
return Math.PI * size * size;
case "square":
return size * size;
case "triangle":
return (size * size) / 2;
default:
// default に到達したら never に代入→コンパイルエラーでケース漏れを検出
const _exhaustive: never = shape;
throw new Error(`未対応の shape: ${_exhaustive}`);
}
}
// Shape に "pentagon" を追加した場合:
// type Shape = "circle" | "square" | "triangle" | "pentagon";
// → default の `never = shape` でコンパイルエラーが発生し、ケース追加を促せる
Union 型に新しいケースが追加された際に、switch/if の処理が漏れていればコンパイルエラーで即座に気づけるのが never の最大の活用法です。型定義とロジックが自動的に同期されます。
4-3. 戻り値 never の関数
// 必ず例外を throw する関数
function fail(message: string): never {
throw new Error(message);
}
// 無限ループで絶対に return しない関数
function infiniteLoop(): never {
while (true) {
// 処理...
}
}
// never 関数はどんな型の文脈でも使える(ボトム型の特性)
function getUser(id: number): User {
const user = db.find(id);
if (!user) {
return fail(`User ${id} not found`); // never は User 型として受け入れられる
}
return user;
}
4-4. 条件型での never による型フィルタリング
// Exclude: Union 型から特定の型を除外 type Status = "active" | "inactive" | "deleted"; type ActiveStatus = Exclude<Status, "deleted">; // → "active" | "inactive" // 内部的には条件型で never を使っている // type Exclude<T, U> = T extends U ? never : T; // NonNullable: null と undefined を除外 type MaybeString = string | null | undefined; type DefiniteString = NonNullable<MaybeString>; // → string // カスタムフィルタ: 配列型のみ抽出 type ArrayTypes<T> = T extends any[] ? T : never; type OnlyArrays = ArrayTypes<string | number[] | boolean | string[]>; // → number[] | string[]
条件型の詳細は 高度な型(条件型・テンプレートリテラル型・マップ型)完全ガイド を参照してください。
5. 実践例3本
実践例1:APIレスポンスを unknown で安全に受け取る
外部APIのレスポンスは型が保証されません。unknown で受け取り、型ガード関数で検証してから使うパターンです。
interface Product {
id: number;
name: string;
price: number;
}
// 型ガード関数: unknown → Product かどうかを検証
function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
"id" in value && typeof (value as Product).id === "number" &&
"name" in value && typeof (value as Product).name === "string" &&
"price" in value && typeof (value as Product).price === "number"
);
}
async function fetchProduct(id: number): Promise<Product> {
const res = await fetch(`/api/products/${id}`);
const data: unknown = await res.json(); // unknown で受け取る
if (!isProduct(data)) {
throw new Error(`Invalid product data: ${JSON.stringify(data)}`);
}
return data; // 型ガード後は Product 型として扱える
}
実践例2:エラーメッセージを unknown から安全に取り出す
catch ブロックの error は unknown のため、ユーティリティ関数を作っておくとすっきりしたエラーハンドリングが書けます。
// エラーからメッセージを安全に取り出すユーティリティ
function toErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
if (typeof error === "object" && error !== null && "message" in error) {
return String((error as { message: unknown }).message);
}
return "不明なエラーが発生しました";
}
// 使用例
async function loadData(url: string): Promise<void> {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data: unknown = await res.json();
console.log("取得成功:", data);
} catch (error: unknown) {
// NG: error.message // Object is of type 'unknown'
console.error("失敗:", toErrorMessage(error)); // OK
}
}
実践例3:Union 型の網羅性チェックで将来の変更に備える
アプリの状態管理など、Union 型のパターンが増える設計では、never による網羅性チェックを入れておくと、将来のケース追加漏れをコンパイルエラーで検出できます。
type OrderStatus =
| { type: "pending"; orderId: string }
| { type: "shipped"; orderId: string; trackingId: string }
| { type: "delivered"; orderId: string; deliveredAt: Date }
| { type: "cancelled"; orderId: string; reason: string };
// never でケース漏れをコンパイル時に検出
function assertNever(value: never): never {
throw new Error(`未処理のケース: ${JSON.stringify(value)}`);
}
function getStatusMessage(status: OrderStatus): string {
switch (status.type) {
case "pending":
return `注文受付中: ${status.orderId}`;
case "shipped":
return `発送済み(追跡: ${status.trackingId})`;
case "delivered":
return `配達完了: ${status.deliveredAt.toLocaleDateString()}`;
case "cancelled":
return `キャンセル(理由: ${status.reason})`;
default:
return assertNever(status);
// OrderStatus に新しい type を追加すると、ここでコンパイルエラーが発生する
}
}
判別可能なUnion型の詳細は 判別可能なユニオン型(Discriminated Unions)完全ガイド を参照してください。
6. まとめ:使い分け早見表
| 状況 | 推奨する型 | 理由 |
|---|---|---|
| 型がわかっている | 具体的な型 / interface | 最も型安全 |
| 外部APIレスポンス・JSON.parse | unknown |
受け取り後に型ガードで検証 |
| catch ブロックの error | unknown(TS4.0以降) |
instanceofで絞り込み必須 |
| 型定義のないJS資産・移行期 | any(一時的) |
型安全化が目標・長期的には除去 |
| 必ずthrowする関数の戻り値 | never |
呼び出し元に「返らない」を伝える |
| switch/if の網羅性保証 | never(デフォルト節) |
ケース漏れをコンパイルエラーで検出 |
| Union 型から特定の型を除外 | never(条件型) |
Exclude / NonNullable の内部実装 |
TypeScript の型システム全般については 型の書き方 完全入門 も参照してください。
FAQ
Qany と unknown はどちらも「型がわからないとき」に使うと聞きました。違いは何ですか?
A最大の違いは操作する前に型を確認する必要があるかどうかです。any は確認なしに何でも操作できてしまいますが(型チェックが無効)、unknown は typeof や instanceof などで型を確認しないと操作できません。外部から入力を受け取る場合は unknown が推奨されます。
QnoImplicitAny オプションとは何ですか?
AnoImplicitAny: true は「型が推論できない変数に暗黙的に any 型が付くことを禁止する」tsconfig オプションです。有効にすることで、any になりそうな箇所を明示的に型付けすることを強制し、型安全性を高められます。strict モードに含まれており、新規プロジェクトでは有効化が推奨されます。
Qnever 型の変数に代入できるものはありますか?
Anever はボトム型(すべての型のサブタイプ)なので、never 型の値を代入できる型はありません(never 自身も含め)。実際に never 型の変数に値を代入しようとするとコンパイルエラーになります。これを逆手にとって「ここには絶対に到達しないはず」という保証に使います。
Qcatch の error が unknown になって困っています。従来の書き方に戻せますか?
Atsconfig.json で "useUnknownInCatchVariables": false を設定すると、catch の変数が再び any 型になります。ただし、型安全性が下がるため推奨しません。代わりに getErrorMessage(error: unknown): string のようなユーティリティ関数を1つ作っておくと、各 catch で簡潔に安全なエラーハンドリングができます。
Qunknown vs object の違いは何ですか?
Aobject はオブジェクト型(プリミティブ以外)を表し、unknown はあらゆる型を受け付けます。object 型の変数には文字列・数値・真偽値などのプリミティブは代入できませんが、unknown にはすべての値が代入可能です。「型がわからない」を表すには unknown が適切です。
Qnever と void の違いは何ですか?
Avoid は「戻り値がない(undefined を返す)」関数の戻り値型で、undefined は代入できます。一方 never は「絶対に return しない」関数の型で、throw か無限ループのみが該当します。通常の return; で終わる関数は void、throw のみの関数は never になります。

