2022年4月8日 りあクト! 第4章 型アサーションと型ガード 型ガードでスマートに型安全を保証する (p.203~)
型ガード
if文の条件式などで型を判定して、スコープ内で型を保証することを型ガードと言います。
typeof
やinstanceof
を用いて型ガードを行う例を紹介します。
typeofによる型ガード
const foo: unknown = '1,2,3,4'; if (typeof foo === 'string') { console.log(foo.split(',')); } console.log(foo.split(',')); // compile error!
- typeof で型ガードを行なっています。
- typeofによってstring型だと判断されたブロック内では、変数fooに stringのプロトタイプメソッドである split()が使えています。
instanceofによる型ガード
class Base { common = 'common'; } class Foo extends Base { foo = () => { console.log('foo');}} class Bar extends Base { bar = () => { console.log('bar');}} const doDivide = (arg: Foo | Bar) => { if (arg instanceof Foo) { arg.foo(); arg.bar(); // comopie error! } else { arg.bar(); arg.foo(); // compile error! } console.log(arg.common); } doDivide(new Foo()); doDivide(new Bar());
- instanceof Fooのブロック内ではfooクラスの型に絞り込まれていることがわかります。
- instanceof Barのブロック内ではbarクラスの型に絞り込まれていることがわかります。
ユーザー定義型ガード
型の絞り込み (type narrowing) に使うことができる関数です。
戻り値の型を引数名 is 型
( 型述語と呼ばれる表現 )として定義した関数がtrueを返す場合に引数argの型がUserであることがコンパイラに示唆されます。
type User = { username: string; address: { zipcode: string; town: string}}; const isUser = (arg: unknown): arg is User => { const u = arg as User; return ( typeof u?.username === 'string' && typeof u?.address?.zipcode === 'string' && typeof u?.address?.town === 'string' ); }; const u1: unknown = JSON.parse('{}'); const u2: unknown = JSON.parse('{"username": "patty", "address": "Maple Town" }'); const u3: unknown = JSON.parse('{"username": "patty", "address": { "zipcode": "111", "town": "Maple Town" }}', ); [u1, u2, u3].forEach((u) => { if (isUser(u)) { console.log(`${u.username} lives in ${u.address.town}`); } else { console.log("It is not User"); console.log(`${u.username} lives in ${u.address.town}`); // compile error! } });
- isUserでは型アサーションで引数をUser型に断定することでreturn文の中でプロパティにアクセスすることができています。
- isUserは真偽値を返しますが、返り値の型をコンパイラに伝えることが出来ます。
型ガードのわかりやすい例
ユーザー定義型ガードを適用させる前
const isString = (a: unknown): boolean => { return typeof a === 'string'; }; // 型の絞り込みは、呼び出し元のスコープには引き継がれない // TypeScriptが理解しているのは、isStringがbooleanを返したということだけです。 const parseInput = (input: string | number) => { let formattedInput: string if(isString(input)) { formattedInput = input.toUpperCase() } }
ユーザー定義型ガード
// Type predicateの宣言は、戻り値がboolean型の関数に対して適用できる const isString = (a: unknown): a is string => { return typeof a === 'string'; }; // 呼び出し元のスコープが型ガードの内容を引き継ぐことができる const parseInput = (input: string | number) => { let formattedInput: string if(isString(input)) { formattedInput = input.toUpperCase() } }
- 型ガードを使わないと型自体は呼び出し元のスコープに引き継がれません。
a is string
とする事でisString
の返り値にstring
型を持たせられています。