投稿日:2021年2月23日
まずは簡単な例からはじめましょう。
以下のようなUserというクラスと、そのクラスのオブジェクトを引数として受け取るlogInformationという関数があったとします。
/**
* フィールドがpublicなクラス
*/
class User {
constructor(public id: number, public name: string) {}
getInfo() {
return `${this.id} : ${this.name}`;
}
}
/**
* Userの情報をログに出力する関数
* @param user
*/
function logInformation(user: User) {
console.log(`${user.id} : ${user.name}`);
}
TypeScriptの場合、オブジェクトのshapeが仮にUserクラスと同じであっても、そのオブジェクトをそのままUserとしてあつかうことはできません。
const user = { id: 1, name: "marsquai" };
logInformation(user); // [ERROR] Argument of type '{ id: number; name: string; }' is not assignable to parameter of type 'User'.
このような場合、型アサーション(as)を使ってトランスパイル時のエラーを回避できます。
const user = { id: 1, name: "marsquai" } as User;
logInformation(user); // [ OK ]
型アサーションで対象の型として扱えるオブジェクトは型アサーションする型の部分集合である必要があります。
オブジェクト⊆型アサーションの型
以下にいくつかの例をしめしておきます。
class User {
constructor(public id: number, public name: string) {}
getId() {
return this.id;
}
getName() {
return this.name;
}
}
//const user = { id: 1, name: "marsquai" } as User;
const user1 = { id: 1, name: "marsquai" } as User; // [ OK ]
const user2 = { id: 2 } as User; // [ OK ]
const user3 = { id: 3, age: 16 } as User; // [ Error ]
const user4 = {} as User; // [ OK ]
const user5 = { getId() {} } as User; // [ OK ]
const user6 = { getId():string {return "4"}} as User // [ ERROR ]
ここで型アサーションはキャストではないことに注意してください。
型アサーションはあくまでトランスパイル時にオブジェクトを指定された型としてみなすだけであって、オブジェクト自体に何らかの変更が加えられているわけではありません。
以下のようなコードはトランスパイルは問題なく通りますが実行時にエラーになってしまいます。
const user = { id: 1, name: "marsquai" } as User;
user.getInfo();
user.getInfo();
^
TypeError: user.getInfo is not a function
userはトランスパイル時にはUserクラスのオブジェクトとして扱われますが、実際にはUserクラスのオブジェクトではないのです。
トランスパイルにエラーが発見できないコードは非常に危険です。
型アサーションの危険性については十分説明したので、実際にどのような場合に使う可能性があるのでしょうか?
一番の用途がJavaScriptやJSONなど型のないオブジェクトからTypeScriptへのデータ変換時です。
例えば、サーバーから送られるデータをパースしたい場合、JSON.parse()を使うことになると思いますが…
JSON.parse()の返り値の型はany型です。
TypeScriptでany型を使いたくないのは万人に共通する事項でしょう。
このような場合の解法として一旦型アサーションで型を指定し、必要なデータのみをとりだした後クラスのインスタンスを返す、という手法があります。
実際の実装例を見てみましょう。
// サーバーから受けったと仮定したデータ
const DATA_BODY =
'[{"name": "marsquai", "id" : 1},{"name": "ogihara", "id" : 2},{"name": "akky", "id" : 3}]';
function getUser(id: number): User {
const usersData = JSON.parse(DATA_BODY) as User[]; // 一旦Userの配列に型アサーション
const userSelected = usersData.filter((d) => d.id == id); // 必要なデータのみ取得
if (userSelected.length > 0) {
return new User(userSelected[0].id, userSelected[0].name); // インスタンス化して返す
} else {
return new User(id, "no name");
}
}
型アサーションしたデータについてデータの整合性についてのチェックを行っていない点に注目してください。
今回、フロントがサーバーから受け取ったデータについて処理をしているというケースを想定しています。フロントのHTTPリクエストに対してサーバーが謎なHTTPレスポンスを返すということは基本的にありえません。仮にもし、そのようなレスポンスが帰る場合には完全にバグが発生している状況と言えるのでエラーを表示しても問題ないですね。
ただし逆に、サーバーがフロントからのJSONリクエストを受け取りそれをパースする場合については、値のバリデーションやクラスのインスタンス化などをするほうが良いでしょう。