ユーザー定義の型ガード関数
型ガードを全てのコードに細かく書いていると非常に冗長になります。型ガードは関数として定義できます。型ガードを関数化するには、関数の戻り値の記述を少し変えます。
例を示します。
class Human {
punch(): void { console.log("Punch!") }
}
class Mage extends Human {
cast(): void { console.log("Cast Magick!") }
}
function isMage(chara: Human): chara is Mage {
return chara instanceof Mage;
}
function attack(chara: Human) {
if (isMage(chara)) {
chara.cast();
} else {
chara.punch();
}
}
let chara = new Human();
let mage = new Mage();
attack(chara);
attack(mage);
isMage関数の戻り値の注釈chara is Mageが型ガード関数に必要な記述です。
attack関数では、isMage関数を使って型ガードを行っています。
any型とunknown型
any型は、あらゆる値を許容する型です。何も型を設定しないのと同義です。TypeScriptにしたけど、とりあえず何か型を書いておくといったときに使います。
unknown型も何でもありですが、any型よりも安全な型です。unknown型の変数は、他の型の変数に代入できません。また、メソッドが使えません。型ガードで型を絞り込んだあとに実際の処理を行っていきます。
let p1: unknown = 100;
let p2: unknown = p1; // これは大丈夫
let p3: number = p1; // これはエラー
console.log(p1.toFixed(0)); // これはエラー
if (typeof p1 === "number") {
console.log(p1.toFixed(0)); // これは大丈夫
}
読み込んだJSONデータに対処してみる
それでは、外部からデータを読み込んだと想定して、JSON文字列から作成したオブジェクトに対して処理を行ってみましょう。
仮の文字列データ'{name: "Tom", age: 10}'を利用した処理を書きます。
type User = {
name: string;
age: number;
};
function isUser(obj: any): obj is User {
return (
typeof obj === 'object' && obj !== null &&
typeof obj.name === 'string' &&
typeof obj.age === 'number'
);
}
function dumpUser(obj: any) {
if (isUser(obj)) {
let len: number = obj.name.length;
let fix: string = obj.age.toFixed(0);
console.log(`${obj.name}: len ${len}`);
console.log(`${obj.age}: fixed ${fix}`);
}
}
let json = '{name: "Tom", age: 10}';
dumpUser(JSON.parse(json))
Userという型と、isUserという型ガード関数を用意して、dumpUser関数内の処理を書いています。
if (isUser(obj))のブロックの中で、nameにマウスカーソルを重ねてください。(property) name: stringと表示されます。nameが文字列であると絞り込まれています。同様にageにマウスカーソルを重ねると(property) age: numberと表示されます。ageが数値であると絞り込まれています。
このように型ガードを使うことで、外部から読み込んだデータを、TypeScriptの静的解析で扱うことができるようになります。
