型ガードとは?
参考: https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard
Type Guardを使用すると、条件ブロック内のオブジェクトの型を制限することができます。
簡単に言うと、複数の型が想定されるものを条件分岐で判断すること
typeof
やinstanceof
などが当てはまる
function doSomething(x: number | string) {
if (typeof x === 'string') { // Within the block TypeScript knows that `x` must be a string
console.log(x.subtr(1)); // Error, 'subtr' does not exist on `string`
console.log(x.substr(1)); // OK
}
x.substr(1); // Error: There is no guarantee that `x` is a `string`
}
class Foo {
foo = 123;
common = '123';
}
class Bar {
bar = 123;
common = '123';
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // OK
console.log(arg.bar); // Error!
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error!
console.log(arg.bar); // OK
}
console.log(arg.common); // OK
console.log(arg.foo); // Error!
console.log(arg.bar); // Error!
}
doStuff(new Foo());
doStuff(new Bar());
上記の例のように引数などで複数の型が想定される場合に、それぞれの型の場合で処理を分岐したい時に利用することができます
is演算子とはなにか?
ref: https://zenn.dev/tsuboi/articles/0bace937cbd0787191cb
is演算子を使うことで、条件式を切り出すこと(スコープから離れること)が可能となります。is演算子は、開発者が TypeScript に対して型を教えるための機能で、ユーザー定義型ガードと呼ばれます。関数の返り値を引数 X is Tとアノテートすると、条件がtrueを返す場合はXはTであり、falseを返す場合はTではないとTypeScriptに指示することになります。
複数の型をis演算子を使うことで返り値をその型として扱うことができます
function doSomething(x: number | string) {
const isString = (x: number | string): x is string => typeof x === "string";
if (isString(x)) {
console.log(x.toUpperCase());
} else {
console.log(x.toFixed(2));
}
}
doSomething("max"); //MAX```
isStringは、返り値をx is numberとアノテートしているため、trueを返した場合はargはnumberであると見做されます。そのため、ifブロックのなかではxがstringに絞り込まれています。この機能を使うことで、型ガードを関数として切り出すことが可能になり、複雑な型ガードを実装することも可能となります。
//is演算子による型ガード
const isString = (arg: number | string): arg is string => typeof x === "string";
function doSomething(x: number | string) {
if (isString(x)) {
console.log(x.toUpperCase());
} else {
console.log(x.toFixed(2));
}
}
doSomething("max"); //MAX
type User = { username: string; address: { zipcode: string; town: string } };
type Guest = { username: string; };
const isUser = (arg: User | Guest): arg is User => {
const u = arg as User;
return (
typeof u?.username === 'string' &&
typeof u?.address?.zipcode === 'string' &&
typeof u?.address?.town === 'string'
);
};
このisUser関数ではisの型述語が使われているが、つまりはその返り値が true だったときにその型がUserであることをコンパイラに示唆する。 false であればGuestであることを示唆する。
const u: unknown = JSON.parse('{ "username": "patty" }');
if (isUser(u)) {
console.log(`${u.username} lives in ${u.address.town}`);
}else{
console.log("It's Guest");
console.log(`${u.username} lives in ${u.address.town}`); // Guestにはaddressがないのでコンパイルエラー!
}
ユースケース「配列のstring | undefined[]
からundefinedを取り除く」
const items: (string | undefined)[] = ['一番', undefined, '2番', undefined, '3番']
const stringItems: string[] = items.filter((item): item is string => typeof item == 'string')
このように指定してあげるとfilterが判断できるのでstring型の配列として扱うことができます