本日はTypeScriptにおけるany型とunknown型の違いを解説していきます。
どちらも型が決まっていないときに使う、という意味では同じものに見えるのですが大きな違いがあり、初学者の方には共有されていないことが多いです。
結論から言うとany型はあまりおススメできません、unknown型を積極的に使うべきだと考えます。
TypeScriptはJavaScriptの延長線上の言語のため、「TypeScript初めて、、、」という方でも分かるように解説してきますので安心してください。
また動画もあるので必要に応じて使ってください。
TypeScriptの基本的な書き方
それではまず基本的なTypeScriptの書き方から紹介していきます。
TypeScriptが初めての方はこれだけでも覚えてもらえればOKです。
const test: string = "テストです";
TypeScriptは基本的にはJavaScriptで使う文法をそのまま使用します。
上記のコードは定数testに文字列で「テストです」を格納しているだけのものです。
TypeScriptの大きな特徴としては定数の名前に「:」で続けて型を指定するルールがあることです。
上記のコードでは文字列なので「string」としました。
「型」と一言に言っても何種類かあり、何の型にするかは開発者が決めます。
・string:文字列
・number:数字
・boolean:真偽値(true,false)
など
TypeScriptにおけるany型とunknown型の大きな違い
TypeScriptでは型を指定することが分かりました。
そして型にはいくつか種類があり、その中にタイトルにもなっている「any」「unknown」があるんです。
// 文字をany型で定義
const testAny: any = "any";
// 文字をunknown型で定義
const testUnknown: unknown = "unknown";
any型もunknown型もザックリいうと「何でも良い型」というものになります。
作り手の都合として文字でも数字でも何であったとしてもプログラムに支障がないときに使用する型になります。
学習教材やYoutube動画でも「特に指定するものがないときはanyにしましょう」という説明がされているはずです。
と言いながらも実際のところany型は少々リスクがあります。
上記のコードでは文字列に対して、それぞれanyとunknownを指定しました。
今度はオブジェクトを作りたいと思います。
オブジェクトには一つのプロパティでも何種類も型
// オブジェクトをany型で定義
const objAny: any = {
id: "0001",
name: "aaa",
};
// オブジェクトをunknown型で定義
const objUnknown: unknown = {
id: "0001",
name: "aaa",
};
さらに定義したオブジェクトについてプロパティにアクセスしてみます。
const objAny: any = {
id: "0001",
name: "aaa",
};
const objUnknown: unknown = {
id: "0001",
name: "aaa",
};
//ここから追記
console.log(objAny.id); // "0001"にアクセス
console.log(objUnknown.id); // コンパイルエラーや警告文が出る
それぞれオブジェクト名にドットでつなげてプロパティ名を指定すると、プロパティの値にアクセスできるはずなんですがunknown型のオブジェクトのみアクセスできなくなります。
これがany型とunknown型の大きな違いになります。
unknown型のオブジェクトを操作する方法
「じゃあunknown型のオブジェクトを操作するにはどうすればいいのか?」という話になるのですが、以下のような形でプロパティごとに型を定義することでアクセスできるようになります。
// 各プロパティごとに型を定義しておく
type ObjType = {
id: string;
name: string;
};
const objKnown: unknown = {
id: "0001",
name: "bbb",
};
// 事前に定義された型を使うことを宣言するとunknown型のオブジェクトを操作できる
console.log((objKnown as ObjType).id);
TypeScriptでは「type タイプ名」とすることで事前に型を定義して、テンプレートのように自分で型の組み合わせて色んな場所で自分で作ったテンプレートを使い回すことができるようになっています。
また型のテンプレートを使い回す際には「as」で繋げて、使い回すタイプ名を指定します。
type ObjType = {
id: string; // idは文字列型
name: string; // nameは文字列型
};
~省略~
// 事前に定義されたObjTypeというテンプレートの型を使う
console.log((objKnown as ObjType).id);
unknown型はオブジェクトの操作についてのみ、プロパティごとに型を定義が必要になるという特徴を持っています。
そのため事前にテンプレートの型を用意しておいて、オブジェクトを操作するときに「as」でつなげてテンプレートの型を指定するとany型のようにオブジェクトを操作することができます。
any型もunknown型も「何でも良い」という意味ではありながらも、unknown型のほうがオブジェクトを操作するときのみ型チェックを残すことができるんです。
なぜany型は好ましくないのか?
今度はany型の話に戻りますが、any型は事前にテンプレートで型を作っておく必要もなく「自由にできる」ように見えます。
たしかに言ってることは正しいのですが、実務においては「気付けた間違いをスルーしてしまう」というリスクがあります。
any型もunknown型も「何でも良い」という意味ではありますが、実際にオブジェクトのメソッドを動作させてみると「やっぱり特定の型に限定しないといけなかった」という場面があります。
any型だと「型チェックしないといけないメソッド」コンパイルエラーで止めずに強引に進めてしまうので、バグが起きたときには既に本番環境に移っていたということになる可能性があります。
そもそもTypeScriptを使っている時点ですべてのデータに対して型チェックをすることが理想なんですが、「そうは言っても初めて触るAPIのデータだと何の型が良いのか分からない」というタイミングもあるかと思います。
そういった場合にはunknown型を使用することをおススメします。