【TypeScript】unknown・any・never の違いと使い分け完全ガイド|型安全なコードを書く実践テクニック

【TypeScript】unknown・any・never の違いと使い分け完全ガイド|型安全なコードを書く実践テクニック TypeScript

TypeScript を書いていると anyunknownnever という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 を使うと 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 節の変数 errorunknown 型になりました(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` でコンパイルエラーが発生し、ケース追加を促せる
✅ Exhaustive Check でリグレッションを防ぐ
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 ブロックの errorunknown のため、ユーティリティ関数を作っておくとすっきりしたエラーハンドリングが書けます。

// エラーからメッセージを安全に取り出すユーティリティ
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 は確認なしに何でも操作できてしまいますが(型チェックが無効)、unknowntypeofinstanceof などで型を確認しないと操作できません。外部から入力を受け取る場合は 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; で終わる関数は voidthrow のみの関数は never になります。