型ガード

型を守る

昨日書いたこの type。age はあるかどうかわかりません。

type Human = {
  name: string;
  age?: number;
  sex: "M" | "W";
};

age が 12 以下ならキッズだと判断する場合、以下のように書きたいですが、エラーになっちゃいます。

const chkKids = (param: Human) => param.age <= 12;
//TS2532: Object is possibly 'undefined'.

undefined かもしれんから比較できませんやんって言ってきます。
キッズチェックをする前に、型の絞り込みを行う必要があります。ガードガード。

const chkKids = (param: Human) => {
  if (typeof param.age === "number") {
    return param.age <= 12;
  } else {
    return false;
  }
};
console.log(chkKids({ name: "a", age: 13, sex: "M" })); //false
console.log(chkKids({ name: "a", age: 12, sex: "M" })); //true
console.log(chkKids({ name: "a", sex: "M" })); //false

number 以外をチェックし、即 return をしてもガードがかけられます。
number 以外で return したら 後は number しかないからね。

const chkKids = (param: Human) => {
  if (typeof param.age !== "number") {
    return false;
  }
  return param.age <= 12;
};
console.log(chkKids({ name: "a", age: 13, sex: "M" })); //false
console.log(chkKids({ name: "a", age: 12, sex: "M" })); //true
console.log(chkKids({ name: "a", sex: "M" })); //false

ただ、これぐらいなら三項演算子でもできますね。

const chkKids = (param: Human) =>
  typeof param.age !== "number" ? false : param.age <= 12;
console.log(chkKids({ name: "a", age: 13, sex: "M" })); //false
console.log(chkKids({ name: "a", age: 12, sex: "M" })); //true
console.log(chkKids({ name: "a", sex: "M" })); //false

ユーザ定義の型ガード

type で定義した型は、typeof で判断できません。
上記の Human 型かどうか判断するためには、ユーザ定義の型ガードを使います。

const isHuman = (human: object): human is Human => {
  const h = human as Human;
  return (
    typeof h?.name === "string" &&
    typeof h?.sex === "string" &&
    (typeof h?.age === "number" || typeof h?.age === "undefined")
  );
};
console.log(isHuman({ name: "a", age: 13, sex: "M" })); //true
console.log(isHuman({ name: "a", age: 12, sex: "M" })); //true
console.log(isHuman({ name: "a", sex: "M" })); //true
console.log(isHuman({ name: "a" })); //false

human is Human の部分。この関数の型が true を返した場合、この引数は Human だよって絞り込んでくれます。
return で true を返すには、Human 型をそれぞれチェックをかかける必要がありますが、human?.name でチェックすることはできません。
引数 human には name があるか判断できないから。
ってことで、これは Human 型なんやでぇ!と強制的にキャストする型アサーション(Deep Dive)を使っています。

const h = human as Human;

これでコンパイラに認識させることができたので、それぞれの型をチェックして return。これで Human 型かどうかを確かめる関数ができました。

type Human = {
  name: string;
  age?: number;
  sex: "M" | "W";
};

const isHuman = (human: object): human is Human => {
  const h = human as Human;
  return (
    typeof h?.name === "string" &&
    typeof h?.sex === "string" &&
    (typeof h?.age === "number" || typeof h?.age === "undefined")
  );
};

const chkKids = (param: object) => {
  if (!isHuman(param)) return false;
  return typeof param.age !== "number" ? false : param.age <= 12;
};

console.log(chkKids({ name: "a", age: 13, sex: "M" })); //false
console.log(chkKids({ name: "a", age: 12, sex: "M" })); //true
console.log(chkKids({ name: "a", sex: "M" })); //false
console.log(chkKids({ name: "a" })); //false

chkKids の引数を object で受け取りましたが、isHuman で Human 型に絞り込んでいるので、キッズチェックをすることができます。 終わり!