API のレスポンスや JSON.parse の結果のように、「何が入っているか実行してみないとわからない値」を扱う場面があります。こうした値の型として、TypeScript には unknown が用意されています。unknown は「なんでも入るが、使う前に型を確認しなさい」という安全な型です。この記事では、unknown の基本、なんでも許してしまう any との違い、そして値を使えるようにするための「型の絞り込み」を、初心者向けに解説します。
目次
unknown はどんなときに使う型か
unknown は「型がまだ決まっていない値」を表す型です。どんな値でも unknown 型の変数に代入できます。文字列でも数値でもオブジェクトでも、いったん受け止められます。
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 は、この「なんでも入る」利便性を保ちつつ、「使う前に確認させる」ことで安全性を守ります。
any | unknown | |
|---|---|---|
| どんな値も代入できる | できる | できる |
| そのままプロパティ・メソッドを使える | 使える(無制限) | 使えない(要・絞り込み) |
| 他の型の変数に代入 | そのまま代入できる | 絞り込みが必要 |
| 安全性 | 低い(チェックが効かない) | 高い(誤用を防げる) |
次のコードで、両者の違いをはっきりさせましょう。
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 による判定です。
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 演算子で「そのプロパティを持つか」を確かめると、必要なプロパティにアクセスできるようになります。
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 として受け取り、絞り込んでから使うようにすると、想定外のデータでコードが壊れるのを防げます。
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 オブジェクトかを確認しましょう。
try {
throw new Error("失敗しました");
} catch (e: unknown) {
// Error のインスタンスか確認してから message を読む
if (e instanceof Error) {
console.log(e.message); // 失敗しました
} else {
console.log("不明なエラー");
}
}
e.message をいきなり読むと、e が Error でなかった場合に実行時エラーになります。instanceof Error で確認してから使うことで、こうした事故を型の段階で防げます。
まとめ
unknown は「どんな値でも受け取れるが、使う前に型を確認させる」安全な型です。なんでも素通しにする any と違い、typeof や in、instanceof で型を絞り込まないとプロパティやメソッドを使えません。この「ひと手間」があるおかげで、API レスポンスや JSON.parse の結果、catch のエラーのように「実行するまで中身がわからない値」を安全に扱えます。「型がわからないからとりあえず any」と書きたくなったら、まず unknown に置き換えられないか考えてみましょう。それだけで、コードの安全性は一段と高まります。