【TypeScript】型ガードと型アサーションでunknown型を使い勝手良くする

こんにちは、フロントエンドエンジニアのてりーです。

はじめに

TypeScript3.0から登場のunknown型。
any型から進化し、型安全は保証されていますが、
型を特定する処理を加えないと、コンパイルを通してくれません。

そこで今回はunknown型と併用して型を特定してあげる役割を持っている「型アサーション」と「型ガード」についてみていきます。

ちなみにunknownとの抱き合わせの本命は「型ガード」で、「型アサーション」はオマケ程度!って認識でOKです。

 

型アサーション(as)について

型アサーションとは、型定義の上書き機能です。
「アサーション=断定」の意味通り、型推論を押しのけて、型定義を上書きできるので、unknown型を上書きしてコンパイルを通すのですね。

書き方はasの後に上書きしたい型を書きます。

type Foo {
    bar: number;
    bas: string;
}

const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';

普通ならfooは空のオブジェクトとして生成されているので、foo.bar、foo.basは存在せずコンパイルエラーになります。

しかし、型アサーションでFoo型を上書きしているので、エラーにならない訳です。

型アサーションの危険性

大前提として、型アサーションはあまり使わない方が良いです!!!

型アサーションを使えば、任意のタイミングで型を上書きできる →型安全性が全く保証されない

となるからです。

型アサーションの使い所

unknown型との抱き合わせは、基本的に型ガードを使う事が推奨されています。

型アサーションの使い道として、多くのコンパイルエラーをとりあえず消したい時などが有効です。
例えばJsなど型定義がないコードから、Tsへの移行時などで、型がまだちゃんと敷かれていない時などの応急処置に使うイメージですね。

型ガードについて

型ガードを使う事で、ブロック内のオブジェクトの型を制限出来ます。
そうすることで、unknown型のままではコンパイルエラーになっていた処理を安全に通す事ができます。

typeofでプリミティブ型を扱う

まず、プリミティブ型の型ガードにはtypeofを用います。

function doSomething(x: unknown) {
    if (typeof x === 'string') {

         //型ガードにより、xがstring型と判断されている為、substr()が使える
        console.log(x.substr(1)); 
    }
    //unknownのままだと、string型とは特定できないのでエラーになる
    x.substr(1);
}

instance of でクラスインスタンスを扱う

クラスの場合はinstanceofを使います。
使い方はtypeofの時と、ほとんど一緒です。

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(); //Fooの場合は、barはないのでエラー

   //instanceofはeif文の様にlseを使うことも可能
  } else {
    arg.bar();  
    arg.foo(); //Barの場合は、fooはないのでエラー
  }
  console.log(arg.common);
};
doDivide(new Foo());
doDivide(new Bar());

 

ユーザー定義の型ガード(is)

プリミティブ型やクラスインスタンス型と違い、オブジェクト型などに対応する型ガードはTypeScriptには用意がありません。そこで自分たちで型ガードを作る必要が出てきます。

「is」は型ガードの条件式を切り出す事ができ、ユーザー定義の型ガードと呼ばれます。

関数の返り値を引数is Tと定義すると、
「その関数がtrueを返す場合は引数はTであり、falseを返す場合はTではない」という型ガードの条件式になります。

const bar = (arg: number | string) => {
  const isNumber = (arg: number | string): arg is number =>

    //戻り値でtrueを返しているので、argはnumber型になる
    typeof arg === "number";
};

 

ユーザー定義の型ガード(is)も、コンパイラに型を開発者が押し付ける事ができるという点では、
型アサーション(as)と危険度があまり変わらない為、実行時のエラーが出ない様に漏れを無くしましょう。

まとめ

any型からの反動で、とりあえず全てをコンパイルエラーしているレベルのunknown型ですが、
型アサーションと型ガードを使いこなす事で、実際に使い勝手よく書く事ができます。

最後まで読んでいただきありがとうございます。
Twitterもやっているので、興味あればご覧になってください!

参考文献

りあクト! TypeScriptで始めるつらくないReact開発 第3版【Ⅰ. 言語・環境編】
 - 4章 TypeScriptで型をご安全に
TypeScript の型ガードの注意点と解決法
unknown型とタイプガードと私
TypeScriptの as って何です?(型アサーションについて)

 

TypeScriptの入門記事一覧

【TypeScript入門】JavaScriptとの違いや特徴を詳しく解説
【TypeScript入門】基本的な型の定義方法
【TypeScript入門】関数とクラスの型定義(継承と合成)
【TypeScript入門】クラスの型定義(インターフェースと型エイリアスの違い)
【TypeScript】高度な型表現について
【TypeScript】型ガードと型アサーションでunknown型を使い勝手良くする