1. ホーム
  2. TypeScript

【TypeScript】unknown 型の使い方|any との違いと安全に値を扱う方法

Share

API のレスポンスや JSON.parse の結果のように、「何が入っているか実行してみないとわからない値」を扱う場面があります。こうした値の型として、TypeScript には unknown が用意されています。unknown は「なんでも入るが、使う前に型を確認しなさい」という安全な型です。この記事では、unknown の基本、なんでも許してしまう any との違い、そして値を使えるようにするための「型の絞り込み」を、初心者向けに解説します。

unknown はどんなときに使う型か

unknown は「型がまだ決まっていない値」を表す型です。どんな値でも unknown 型の変数に代入できます。文字列でも数値でもオブジェクトでも、いったん受け止められます。

unknown-basic.ts
let value: unknown;

value = "文字列";   // OK
value = 42;         // OK
value = { a: 1 };   // OK
value = [1, 2, 3];  // OK

「なんでも入る」という点だけ見ると any と同じに思えます。しかし決定的に違うのは、unknownそのままでは使えないことです。中身が何かわからない以上、勝手にプロパティを読んだりメソッドを呼んだりするのは危険——TypeScript はそう考えて、使う前に型を確認させます。

any との違い

any は「型チェックを完全にあきらめる」型です。any の値は何をしてもエラーになりません。一見便利ですが、実行時にエラーになるようなコードも素通りしてしまい、TypeScript を使う意味が薄れます。unknown は、この「なんでも入る」利便性を保ちつつ、「使う前に確認させる」ことで安全性を守ります。

anyunknown
どんな値も代入できるできるできる
そのままプロパティ・メソッドを使える使える(無制限)使えない(要・絞り込み)
他の型の変数に代入そのまま代入できる絞り込みが必要
安全性低い(チェックが効かない)高い(誤用を防げる)

次のコードで、両者の違いをはっきりさせましょう。

any-vs-unknown.ts
const a: any = "hello";
a.toUpperCase();   // OK(any なので何でも通る。実は危険な場合もある)
a.foo.bar;         // OK(存在しなくてもエラーにならない)

const u: unknown = "hello";
// u.toUpperCase();  // エラー:unknown のままではメソッドを呼べない
// u.foo;            // エラー:プロパティにアクセスできない

any は存在しないプロパティへのアクセスすら見逃しますが、unknown はメソッド呼び出しの時点で止めてくれます。「とりあえず受け取るが、うっかり誤用はさせない」——これが unknown の価値です。「型がわからないから any」で済ませていた場面の多くは、unknown に置き換えたほうが安全になります。

使う前に型を絞り込む(型ガード)

unknown の値を実際に使うには、「これは文字列だ」「これは数値だ」と型を確認して絞り込む必要があります。この確認のことを型ガードと呼びます。もっとも基本的なのが typeof による判定です。

narrowing.ts
function printLength(value: unknown) {
  // typeof で「文字列である」と確認してから使う
  if (typeof value === "string") {
    // このブロック内では value は string 型として扱える
    console.log(value.length);
  } else {
    console.log("文字列ではありません");
  }
}

printLength("hello");  // 5
printLength(123);      // 文字列ではありません

if (typeof value === "string") のブロックの中では、TypeScript が「value は string だ」と理解してくれるので、value.length のように文字列のプロパティを安全に使えます。オブジェクトの場合は in 演算子で「そのプロパティを持つか」を確かめると、必要なプロパティにアクセスできるようになります。

in-guard.ts
function getName(data: unknown): string {
  // オブジェクトであり、name プロパティを持つかを確認
  if (typeof data === "object" && data !== null && "name" in data) {
    // ここで data.name にアクセスできる
    return String((data as { name: unknown }).name);
  }
  return "名前なし";
}

console.log(getName({ name: "太郎" })); // 太郎
console.log(getName(42));               // 名前なし

typeof data === "object" だけでは null も通ってしまうため、data !== null のチェックも忘れないようにします。これは typeof null"object" を返すという JavaScript の仕様に対応するためのお決まりの書き方です。

実務で unknown が役立つ場面

JSON.parse や API レスポンスを受け取る

外部から来るデータは、こちらが期待した形とは限りません。JSON.parse の戻り値はもともと any ですが、いったん unknown として受け取り、絞り込んでから使うようにすると、想定外のデータでコードが壊れるのを防げます。

parse.ts
function parseUser(json: string): { name: string } | null {
  const data: unknown = JSON.parse(json);

  // 期待する形かどうかを確認してから返す
  if (
    typeof data === "object" &&
    data !== null &&
    "name" in data &&
    typeof (data as { name: unknown }).name === "string"
  ) {
    return { name: (data as { name: string }).name };
  }
  return null; // 想定外の形なら null
}

try…catch の error は unknown で受け取る

もう一つの定番が例外処理です。JavaScript では throw できる値に制限がなく、文字列やオブジェクトも投げられます。そのため catch で受け取る値の型は unknown になります(TypeScript の設定によります)。エラーメッセージを取り出す前に、それが本当に Error オブジェクトかを確認しましょう。

catch.ts
try {
  throw new Error("失敗しました");
} catch (e: unknown) {
  // Error のインスタンスか確認してから message を読む
  if (e instanceof Error) {
    console.log(e.message); // 失敗しました
  } else {
    console.log("不明なエラー");
  }
}

e.message をいきなり読むと、eError でなかった場合に実行時エラーになります。instanceof Error で確認してから使うことで、こうした事故を型の段階で防げます。

まとめ

unknown は「どんな値でも受け取れるが、使う前に型を確認させる」安全な型です。なんでも素通しにする any と違い、typeofininstanceof で型を絞り込まないとプロパティやメソッドを使えません。この「ひと手間」があるおかげで、API レスポンスや JSON.parse の結果、catch のエラーのように「実行するまで中身がわからない値」を安全に扱えます。「型がわからないからとりあえず any」と書きたくなったら、まず unknown に置き換えられないか考えてみましょう。それだけで、コードの安全性は一段と高まります。

参考ページ