mizkog-logo

Web制作の備忘録

HomeZenn

ユーザー定義のType Guard

ユーザー定義のType Guardがなぜ必要か?

以下のように、valueがanyだった場合、if文で絞り込んでもanyとなってしまい、
Type Guardが機能しなくなってしまう。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const foo = (value: any) => {
 if(value.lang === "ja") {
  return value;
  // value: any
 }
 return value;
 // value: any
};

この時に生きてくるのがユーザー定義のType Guard。

ユーザー定義のType Guardの構文

関数の返り値がtrueの場合、is〇〇で指定した型が適用される。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const isUserA = (user: UserA | UserB): user is UserA => {
 return user.lang == "ja"
}

const foo = (value: any) => {
 if(isUserA(value)) {
  return value;
  // value: UserA
 }
 return value;
 // value: any
};

注意点として最後にreturnで返しているvalueの型がanyとなっている。
そもそもvalueはanyとしているので、事前にisUserAで絞り込んでも最終的にanyの可能性が消える訳ではないため、UserBではなくanyとなる。
もし、UserBも絞り込む場合は以下のようにUserBも追加する。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const isUserA = (user: UserA | UserB): user is UserA => {
 return user.lang == "ja"
}

const isUserB = (user: UserA | UserB): user is UserB => {
 return user.lang == "en"
}

const foo = (value: any) => {
 if(isUserA(value)) {
  return value;
  // value: UserA
 }
 if(isUserB(value)) {
  return value;
  // value: UserB
 }
 return value;
 // value: any
};


ユーザー定義のType Guardがよく使われる事例

非同期処理

自分の監視の範囲外からデータを取得する場合にレスポンスに型を付与することが難しい。
今は色々と技術はあるが、非同期処理で得たデータは型が付いていないことが多い。
そのため、ユーザー定義のType Guardを使うことによって解決していく。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const isUserA = (user: UserA | UserB): user is UserA => {
 return user.lang == "ja"
}

const isUserB = (user: UserA | UserB): user is UserB => {
 return user.lang == "en"
}

const foo = async () => {
 const res = await fetch("");
 const json = await res.json();
 if(isUserA(json)) {
  return json.lang
  // lang: "ja"
 }
};

外部のAPIを叩いた時のレスポンスに型をつけたい場合は上記のように使用する。

filter関数

現状、JavaScriptのfilter関数を使っても型まで絞り込むことが出来ない。
それを型付きで絞り込むためにユーザー定義のType Guardを用いる。

通常、以下のようにfilter関数で"ja"のユーザーを絞り込めるが、ここでは型まで絞り込めない。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const isUserA = (user: UserA | UserB): user is UserA => {
 return user.lang == "ja"
}

const isUserB = (user: UserA | UserB): user is UserB => {
 return user.lang == "en"
}

const users: (UserA | UserB)[] = [
 { name: "たなか", lang: "ja" },
 { name: "やまだ", lang: "ja" },
 { name: "ジョニー", lang: "en" },
];

const japanese = users.filter((user) => user.lang === "ja");
// japanese: (UserA | UserB)[]

そこで、isUserAの(user.....)の箇所と、filter()内が同様の構文になっていることに着目し、
以下のように記述することで、型を絞り込むことができる。

type UserA = { name: string; lang: "ja" };
type UserB = { name: string; lang: "en" };

const isUserA = (user: UserA | UserB): user is UserA => {
 return user.lang == "ja"
}

const isUserB = (user: UserA | UserB): user is UserB => {
 return user.lang == "en"
}

const users: (UserA | UserB)[] = [
 { name: "たなか", lang: "ja" },
 { name: "やまだ", lang: "ja" },
 { name: "ジョニー", lang: "en" },
];

const japanese = users.filter(isUserA);
// japanese: UserA[]

const notJapanese = users.filter(isUserB);
// notJapanese: UserB[]