unknown型
TypeScriptのunknown型は、型が何かわからないときに使う型です。
unknown型にはどのような値も代入できます。
ts
letvalue : unknown;value = 1; // OKvalue = "string"; // OKvalue = {name : "オブジェクト" }; // OK
ts
letvalue : unknown;value = 1; // OKvalue = "string"; // OKvalue = {name : "オブジェクト" }; // OK
unknown型は型安全なany型
unknown型はよく「型安全なany型」と言われ、any型と対比されます。
any型はどのような型の変数にも代入できます。
ts
constvalue : any = 10;constint : number =value ;constbool : boolean =value ;conststr : string =value ;constobj : object =value ;
ts
constvalue : any = 10;constint : number =value ;constbool : boolean =value ;conststr : string =value ;constobj : object =value ;
一方、unknown型の値は具体的な型へ代入できません。
ts
constvalue : unknown = 10;constType 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.: number = int value ;constType 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.: boolean = bool value ;constType 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.: string = str value ;constType 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.: object = obj value ;constany : any =value ; // OKconstunknown : unknown =value ; // OK
ts
constvalue : unknown = 10;constType 'unknown' is not assignable to type 'number'.2322Type 'unknown' is not assignable to type 'number'.: number = int value ;constType 'unknown' is not assignable to type 'boolean'.2322Type 'unknown' is not assignable to type 'boolean'.: boolean = bool value ;constType 'unknown' is not assignable to type 'string'.2322Type 'unknown' is not assignable to type 'string'.: string = str value ;constType 'unknown' is not assignable to type 'object'.2322Type 'unknown' is not assignable to type 'object'.: object = obj value ;constany : any =value ; // OKconstunknown : unknown =value ; // OK
number型である変数int
に対しても代入が失敗しているのは、やりすぎではないかと思われるかもしれませんが、不明な型を安全に扱うとこのようになります。
また、unknown型はプロパティへのアクセス、メソッドの呼び出しも許されません。
ts
constvalue : unknown = 10;Object is of type 'unknown'.2571Object is of type 'unknown'.. value toFixed ();constobj : unknown = {name : "オブジェクト" };Object is of type 'unknown'.2571Object is of type 'unknown'.. obj name ;
ts
constvalue : unknown = 10;Object is of type 'unknown'.2571Object is of type 'unknown'.. value toFixed ();constobj : unknown = {name : "オブジェクト" };Object is of type 'unknown'.2571Object is of type 'unknown'.. obj name ;
anyとunknownの特性の違いの詳細は次のページをご覧ください。
📄️ anyとunknownの違い
unknownと型の絞り込み
unknownはanyよりも安全な不明型ですが、そのままでは実用できません。unknownの値を使うには、型を絞り込む必要があります。
型の絞り込みにはtypeof
やinstanceof
などを条件式に含んだif文を使います。これは型ガードと呼ばれます。型ガードで絞り込むと、それ以降の処理では絞り込まれた型として扱えます。
ts
constvalue : unknown = "";// 型ガードif (typeofvalue === "string") {// ここブロックではvalueはstring型として扱えるconsole .log (value .toUpperCase ());}
ts
constvalue : unknown = "";// 型ガードif (typeofvalue === "string") {// ここブロックではvalueはstring型として扱えるconsole .log (value .toUpperCase ());}
この例ではunknown型の変数value
がtypeof
によりifの中ではstring型であることが確定したため、string型のメソッドであるtoUpperCase()
を使えるようになります。
型の絞り込みは、型ガード関数を使う方法もあります。
ts
// 型ガード関数functionisObject (value : unknown):value is object {return typeofvalue === "object" &&value !== null;}constvalue : unknown = {a : 1,b : 2 };// 型ガードif (isObject (value )) {console .log (Object .keys (value ));// ここでは、valueはobject型として扱える}
ts
// 型ガード関数functionisObject (value : unknown):value is object {return typeofvalue === "object" &&value !== null;}constvalue : unknown = {a : 1,b : 2 };// 型ガードif (isObject (value )) {console .log (Object .keys (value ));// ここでは、valueはobject型として扱える}
📄️ 制御フロー分析と型ガードによる型の絞り込み
unknown型を配列型に絞り込む
unknown型を配列型に絞り込みたいときはArray.isArray()
を使います。加えて、さらに配列要素までチェックすると、より安全なチェック処理になります。
ts
functionisNumberArray (value : unknown):value is number[] {if (!Array .isArray (value )) {return false;}returnvalue .every ((e ) => typeofe === "number");}
ts
functionisNumberArray (value : unknown):value is number[] {if (!Array .isArray (value )) {return false;}returnvalue .every ((e ) => typeofe === "number");}
unknown型をオブジェクト型に絞り込む
unknown型をオブジェクト型に絞り込むには、typeof
演算子を用います。
ts
typefrom : string;to : string;title : string;subject : string;};functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}return true;}
ts
typefrom : string;to : string;title : string;subject : string;};functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}return true;}
このままでは、値が本当にEmail
型を満たしているかわかりません。from
などのプロパティまでチェックしていないからです。チェックの正確さを高めるためには、各プロパティの型をチェックする必要があります。
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// 各プロパティのチェックif (typeofvalue .from !== "string") {return false;}return true;}
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// 各プロパティのチェックif (typeofvalue .from !== "string") {return false;}return true;}
上のプロパティチェックは一見問題なさそうですが、実際は次のようなコンパイルエラーが発生します。
ts
// 各プロパティのチェックif (typeofProperty 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.value .!== "string") { from return false;}return true;}
ts
// 各プロパティのチェックif (typeofProperty 'from' does not exist on type 'object'.2339Property 'from' does not exist on type 'object'.value .!== "string") { from return false;}return true;}
これを回避するには、型アサーションを使ってEmail
型に近づけます。このとき、型アサーションはas Email
でも構いませんが、より型安全にするためにunknown型のRecord
を使うのがお勧めです。
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// 型アサーションでvalueをEmail型に近づけるconstvalue asRecord <keyof// 各プロパティのチェックif (typeoffrom !== "string") {return false;}return true;}
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}// 型アサーションでvalueをEmail型に近づけるconstvalue asRecord <keyof// 各プロパティのチェックif (typeoffrom !== "string") {return false;}return true;}
このときのRecord<keyof Email, unknown>
型は次のように、Email
のプロパティがすべてunknown
になった型となります。
ts
typeMayBeEmail =Record <keyof
ts
typeMayBeEmail =Record <keyof
最後に、各プロパティのチェックをすべて実装したチェック処理は次のようになります。
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}constvalue asRecord <keyofif (typeoffrom !== "string") {return false;}if (typeofto !== "string") {return false;}if (typeoftitle !== "string") {return false;}return typeofsubject === "string";}
ts
functionisEmail (value : unknown):value isif (typeofvalue !== "object" ||value === null) {return false;}constvalue asRecord <keyofif (typeoffrom !== "string") {return false;}if (typeofto !== "string") {return false;}if (typeoftitle !== "string") {return false;}return typeofsubject === "string";}
バリデーションライブラリも検討しよう
unknown型からオブジェクト型へ安全に絞り込むには、プロパティをひとつひとつチェックする必要があります。上の例を見た読者の中には、これを実装するのは大変だと思った方もいるのではないでしょうか。
チェックするプロパティ数が多い場合は、次のバリデーションライブラリを導入したほうがよいでしょう。
これらのライブラリはチェック項目を宣言的に実装できるので、実装コストを抑えられたり、チェック処理の間違いが起きにくくなります。
unknownの用途
any型の値をより安全にする
たとえばJSON.parse()
は戻り値がany型です。このまま戻り値を取り回すと、もし存在しないプロパティにアクセスした場合に、実行時エラーになる危険性が残ります。
そこで一旦unknown型にしておくことで、存在しないプロパティへのアクセスにコンパイル時に気づきやすくなります。
ts
constdata : unknown =JSON .parse ("...");
ts
constdata : unknown =JSON .parse ("...");
型アサーションの制約を回避する
通常、型アサーションでは、まったく異なる型は指定できません。
ts
conststr = "a";constConversion 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.num =str as number;
ts
conststr = "a";constConversion 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.num =str as number;
このようなときにunknown型を使うことができます。unknown型はどのような型にも型アサーションできるため、目的の型の前に一度unknown型への型アサーションを挟むテクニックがあります。
ts
conststr = "a";constnum =str as unknown as number;
ts
conststr = "a";constnum =str as unknown as number;
ただし、型アサーションは実際に値の型をキャストしているのではなく、TypeScriptにその型であると認識させているだけなので型安全性の問題は残ります。
try-catchで捕捉される値の型
TypeScriptは4.4になって、投げられた例外がany型としてかunknown型のどちらかで捕捉されるかを選べるようになりました。ですが、標準の設定では投げられた例外はany型なのでunknown型にしたい場合はtsconfig.jsonの設定を変える必要があります。