型ガードを知ったかしない
型ガードってなによ
百聞は一見にしかずということで簡単な例から紹介します.
type Suuji = string | number;
// いその〜,10を足そうぜ!
const juutasu = (x: Suuji) => x + 10;
console.log(juutasu("5")); // 510
ここにSuuji
という型にstring
が入るかnumber
が入るかわからないという恐ろしいコードがあります.そんなSuuji
という型のものを引数として受け取ってから10を足す関数がありますが,これでは,x + 10
という処理はx
がstring
だった場合は文字列の結合となってしまいます.
そこで型ガードです.
型を絞り込む(narrowing)ことによって,その型にしかない処理や,プロパティの呼び出しを型安全に書くことができます.
typeof
を使った型ガード
const juutasu2 = (x: Suuji) => {
if (typeof x === "number") {
return x + 10;
}
if (typeof x === "string") {
return parseInt(x) + 10;
}
};
console.log(juutasu2("5")); // 15
条件分岐を利用して,そのスコープ内での型を限定していきます.
if (typeof x === "number") {
return x * 10;
}
上記のスコープではx
はnumber
の場合のみとなり,10を足すことができます.
if (typeof x === "string") {
return parseInt(x) * 10;
}
こちらのスコープではx
はstring
の場合なので,parseInt()
を使ってnumber
に変換してから10を足しています.
このように型ガードを利用することで型安全を維持することができます.
わりとよくある例
次は少し実用的な例を見ていきましょう.
type Kensi = {
name: string;
hp: number;
swordSkill: string;
};
type Wizard = {
name: string;
hp: number;
magicSkill: string;
};
type Yusya = Kensi | Wizard;
const attack = (player: Yusya) => {
console.log(`${player.name}は${player.swordSkill}を使った`);
// WizardにswordSkillというものは存在しないのでエラー
};
いろんな事情があり,攻撃をするための関数は一つにまとめたいのですが,Skillはジョブによって異なっているので,エラーとなります.
type
プロパティを追加して型ガード
解決方法1...こちらは型定義時にtype
というプロパティ(名前は何でもいい)を作ってあげることで,それを使ってどちらの方か判別できるようにするというものです.
type Kensi = {
type: "kensi"; // 追加
name: string;
hp: number;
swordSkill: string;
};
type Wizard = {
type: "wizard"; // 追加
name: string;
hp: number;
magicSkill: string;
};
以下のように書くことで型ガードされます.
const attack = (player: Yusya) => {
if (player.type === "kensi") {
console.log(`${player.name}は${player.swordSkill}を使った`);
}
if (player.type === "wizard") {
console.log(`${player.name}は${player.magicSkill}を使った`);
}
};
in
を使用してnarrowingする
解決方法2...in
という構文を使うことでそのプロパティが存在するかどうかを判定します.
const attack = (player: Yusya) => {
if ("swordSkill" in player) {
console.log(`${player.name}は${player.swordSkill}を使った`);
}
if ("magicSkill" in player) {
console.log(`${player.name}は${player.magicSkill}を使った`);
}
};
swordSkill
というプロパティが存在していたならKensi
の型であるといった具合に型ガードしていきます.
解決方法3...型を判定する関数を自作する
/* 型を判定する関数 */
const isKensi = (player: Yusya): player is Kensi => {
return "swordSkill" in player; // Kensiにする条件を自作できる
// return player.type === "kensi" // だから,これでもOK
};
is
構文というものを使って戻り値の型を定義することで,narrowingするための関数を作ることができます.このときplayer is Kensi
という型はboolean
になりますので,条件を満たす場合はtrue
を返すようにカスタマイズしてあげればOKです.
これを使って型ガードします.
const attack4 = (player: Yusya) => {
if (isKensi(player)) {
console.log(`${player.name}は${player.swordSkill}を使った`);
} else {
console.log(`${player.name}は${player.magicSkill}を使った`);
}
};
実際にVSCodeなどでコードを書いて,マウスを乗っけて型が制限されていることを確認すると良いでしょう.
おまけ
型ガードが効かないと思ったらawaitがないやんな話
const attack5 = async (player: Promise<Yusya>) => {
// const response = player // await忘れるといろいろうまくいかない
const response = await player;
if (isKensi(response)) {
console.log(response.swordSkill);
} else {
console.log(response.magicSkill);
}
};
少々ニッチですが,await
をつけ忘れてずっと型ガードができなくて悩んでいたことがあったので,おまけで共有します.
参考
Discussion