メインコンテンツまでスキップ

unknown型

TypeScriptのunknown型は、型が何かわからないときに使う型です。

unknown型にはどのような値も代入できます。

ts
let value: unknown;
value = 1; // OK
value = "string"; // OK
value = { name: "オブジェクト" }; // OK
ts
let value: unknown;
value = 1; // OK
value = "string"; // OK
value = { name: "オブジェクト" }; // OK

unknown型は型安全なany型

unknown型はよく「型安全なany型」と言われ、any型と対比されます。

any型はどのような型の変数にも代入できます。

ts
const value: any = 10;
const int: number = value;
const bool: boolean = value;
const str: string = value;
const obj: object = value;
ts
const value: any = 10;
const int: number = value;
const bool: boolean = value;
const str: string = value;
const obj: object = value;

一方、unknown型の値は具体的な型へ代入できません。

ts
const value: unknown = 10;
const int: number = value;
Type 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.
const bool: boolean = value;
Type 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.
const str: string = value;
Type 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.
const obj: object = value;
Type 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.
 
const any: any = value; // OK
const unknown: unknown = value; // OK
ts
const value: unknown = 10;
const int: number = value;
Type 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.
const bool: boolean = value;
Type 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.
const str: string = value;
Type 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.
const obj: object = value;
Type 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.
 
const any: any = value; // OK
const unknown: unknown = value; // OK

number型である変数intに対しても代入が失敗しているのは、やりすぎではないかと思われるかもしれませんが、不明な型を安全に扱うとこのようになります。

また、unknown型はプロパティへのアクセス、メソッドの呼び出しも許されません。

ts
const value: unknown = 10;
value.toFixed();
Object is of type 'unknown'.2571Object is of type 'unknown'.
 
const obj: unknown = { name: "オブジェクト" };
obj.name;
Object is of type 'unknown'.2571Object is of type 'unknown'.
ts
const value: unknown = 10;
value.toFixed();
Object is of type 'unknown'.2571Object is of type 'unknown'.
 
const obj: unknown = { name: "オブジェクト" };
obj.name;
Object is of type 'unknown'.2571Object is of type 'unknown'.

anyとunknownの特性の違いの詳細は次のページをご覧ください。

📄️ anyとunknownの違い

any, unknown型はどのような値も代入できます。

unknownと型の絞り込み

unknownはanyよりも安全な不明型ですが、そのままでは実用できません。unknownの値を使うには、型を絞り込む必要があります。

型の絞り込みにはtypeofinstanceofなどを条件式に含んだif文を使います。これは型ガードと呼ばれます。型ガードで絞り込むと、それ以降の処理では絞り込まれた型として扱えます。

ts
const value: unknown = "";
// 型ガード
if (typeof value === "string") {
// ここブロックではvalueはstring型として扱える
console.log(value.toUpperCase());
}
ts
const value: unknown = "";
// 型ガード
if (typeof value === "string") {
// ここブロックではvalueはstring型として扱える
console.log(value.toUpperCase());
}

この例ではunknown型の変数valuetypeofによりifの中ではstring型であることが確定したため、string型のメソッドであるtoUpperCase()を使えるようになります。

型の絞り込みは、型ガード関数を使う方法もあります。

ts
// 型ガード関数
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
const value: unknown = { a: 1, b: 2 };
// 型ガード
if (isObject(value)) {
console.log(Object.keys(value));
const value: object
// ここでは、valueはobject型として扱える
}
ts
// 型ガード関数
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
const value: unknown = { a: 1, b: 2 };
// 型ガード
if (isObject(value)) {
console.log(Object.keys(value));
const value: object
// ここでは、valueはobject型として扱える
}

📄️ 制御フロー分析と型ガードによる型の絞り込み

TypeScriptは制御フローと型ガードにより、処理の流れに応じて変数の型を絞り込むことができます。

unknown型を配列型に絞り込む

unknown型を配列型に絞り込みたいときはArray.isArray()を使います。加えて、さらに配列要素までチェックすると、より安全なチェック処理になります。

ts
function isNumberArray(value: unknown): value is number[] {
if (!Array.isArray(value)) {
return false;
}
return value.every((e) => typeof e === "number");
}
ts
function isNumberArray(value: unknown): value is number[] {
if (!Array.isArray(value)) {
return false;
}
return value.every((e) => typeof e === "number");
}

unknown型をオブジェクト型に絞り込む

unknown型をオブジェクト型に絞り込むには、typeof演算子を用います。

ts
type Email = {
from: string;
to: string;
title: string;
subject: string;
};
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
return true;
}
ts
type Email = {
from: string;
to: string;
title: string;
subject: string;
};
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
return true;
}

このままでは、値か本当にEmail型を満たしているかわかりません。fromなどのプロパティまでチェックしていないからです。チェックの正確さを高めるためには、各プロパティの型をチェックする必要があります。

ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
// 各プロパティのチェック
if (typeof value.from !== "string") {
return false;
}
return true;
}
ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
// 各プロパティのチェック
if (typeof value.from !== "string") {
return false;
}
return true;
}

上のプロパティチェックは一見問題なさそうですが、実際は次のようなコンパイルエラーが発生します。

ts
// 各プロパティのチェック
if (typeof value.from !== "string") {
Property 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.
return false;
}
 
return true;
}
ts
// 各プロパティのチェック
if (typeof value.from !== "string") {
Property 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.
return false;
}
 
return true;
}

これを回避するには、型アサーションを使ってEmail型に近づけます。このとき、型アサーションはas Emailでも構いませんが、より型安全にするためにunknown型のRecordを使うのがお勧めです。

ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
// 型アサーションでvalueをEmail型に近づける
const email = value as Record<keyof Email, unknown>;
// 各プロパティのチェック
if (typeof email.from !== "string") {
return false;
}
return true;
}
ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
// 型アサーションでvalueをEmail型に近づける
const email = value as Record<keyof Email, unknown>;
// 各プロパティのチェック
if (typeof email.from !== "string") {
return false;
}
return true;
}

このときのRecord<keyof Email, unknown>型は次のように、Emailのプロパティがすべてunknownになった型なります。

ts
type MayBeEmail = Record<keyof Email, unknown>;
type MayBeEmail = { from: unknown; to: unknown; title: unknown; subject: unknown; }
ts
type MayBeEmail = Record<keyof Email, unknown>;
type MayBeEmail = { from: unknown; to: unknown; title: unknown; subject: unknown; }

最後に、各プロパティのチェックをすべて実装したチェック処理は次のようになります。

ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
const email = value as Record<keyof Email, unknown>;
if (typeof email.from !== "string") {
return false;
}
if (typeof email.to !== "string") {
return false;
}
if (typeof email.title !== "string") {
return false;
}
return typeof email.subject === "string";
}
ts
function isEmail(value: unknown): value is Email {
if (typeof value !== "object" || value === null) {
return false;
}
const email = value as Record<keyof Email, unknown>;
if (typeof email.from !== "string") {
return false;
}
if (typeof email.to !== "string") {
return false;
}
if (typeof email.title !== "string") {
return false;
}
return typeof email.subject === "string";
}
バリデーションライブラリも検討しよう

unknown型からオブジェクト型へ安全に絞り込むには、プロパティをひとつひとつチェックする必要があります。上の例を見た読者の中には、これを実装するのは大変だと思った方もいるのではないでしょうか。

チェックするプロパティ数が多い場合は、次のバリデーションライブラリを導入したほうがよいでしょう。

これらのライブラリはチェック項目を宣言的に実装できるので、実装コストを抑えられたり、チェック処理の間違いが起きにくくなります。

unknownの用途

any型の値をより安全にする

たとえばJSON.parse()は戻り値がany型です。このまま戻り値を取り回すと、もし存在しないプロパティにアクセスした場合に、実行時エラーになる危険性が残ります。

そこで一旦unknown型にしておくことで、存在しないプロパティへのアクセスにコンパイル時に気づきやすくなります。

ts
const data: unknown = JSON.parse("...");
ts
const data: unknown = JSON.parse("...");

型アサーションの制約を回避する

通常、型アサーションでは、まったく異なる型は指定できません。

ts
const str = "a";
const num = str as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
ts
const str = "a";
const num = str as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

このようなときにunknown型を使うことができます。unknown型はどのような型にも型アサーションできるため、目的の型の前に一度unknown型への型アサーションを挟むテクニックがあります。

ts
const str = "a";
const num = str as unknown as number;
ts
const str = "a";
const num = str as unknown as number;

ただし、型アサーションは実際に値の型をキャストしているのではなく、TypeScriptにその型であると認識させているだけなので型安全性の問題は残ります。

try-catchで捕捉される値の型

TypeScriptは4.4になって、投げられた例外がany型としてかunknown型のどちらかで捕捉されるかを選べるようになりました。ですが、標準の設定では投げられた例外はany型なのでunknown型にしたい場合はtsconfig.jsonの設定を変える必要があります。

📄️ useUnknownInCatchVariables

例外捕捉catch(e)のeをunknown型として扱う
  • 質問する ─ 読んでも分からなかったこと、TypeScriptで分からないこと、お気軽にGitHubまで🙂
  • 問題を報告する ─ 文章やサンプルコードなどの誤植はお知らせください。