Help us understand the problem. What is going on with this article?

【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);
}

instanceofでクラスインスタンスを扱う

クラスの場合は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型ですが、
型アサーションと型ガードを使いこなす事で、実際に使い勝手よく書く事ができます。


フリーランスでフロントエンドエンジニアをしています。
お仕事のご相談こちらまで
gunners6518@gmail.com

技術ブログ

Twitterでも発信しています。
https://twitter.com/teriteriteriri

他のTypeScriptに関する記事一覧

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

参考文献

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

terry_6518
フリーランス2年目のフロントエンドエンジニアです。 現在はReact+TypeScriptの開発をメインで扱っています。 Vue.js、Angular、Reactのいずれも実務でメインで扱っていた時期があります。
https://terrblog.com/
engineerlife
技術力をベースに人生を謳歌する人たちのコミュニティです。
https://community.camp-fire.jp/projects/view/280040
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away