6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年4月8日 りあクト! 第4章 型アサーションと型ガード 型ガードでスマートに型安全を保証する (p.203~)

型ガード

if文の条件式などで型を判定して、スコープ内で型を保証することを型ガードと言います。 typeofinstanceofを用いて型ガードを行う例を紹介します。

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 型を持たせられています。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.203