型アサーション(as)とは?型アノテーションとの違いなどをわかりやすく解説!

この記事ではTypeScriptの『型アサーション(as)』について、

  • 型アサーション(as)とは
  • 「型アサーション」と「型アノテーション(型注釈)」の違い
  • 型アサーションの使い方

などを図を用いて分かりやすく説明するように心掛けています。ご参考になれば幸いです。

型アサーション(as)とは

TypeScriptではコードをヒントにして型推論をしてくれますが、型アサーション(Type Assertion)を用いると、「型推論によって推論された型」や「すでに型定義されている型」を上書きすることができます。

まず、以下の型アサーションを用いていないサンプルコードを見てみましょう。

let test = {}; // testの型はプロパティを持たないオブジェクトだと型推論する
test.foo = 123;
// 以下のコンパイラエラーが発生
// Property 'foo' does not exist on type '{}'.
// (プロパティ 'foo' は型 '{}' に存在しません。)

上記のサンプルコードでは、testの型はプロパティを持たないオブジェクトだとコンパイラによって型推論されるため、testにプロパティを追加することができません(上記のサンプルコードではfooプロパティを追加しようとしています)。そのため、コンパイルエラーが発生してしまいます。これを解決するのが型アサーションです。

型アサーションを用いたサンプルコードを以下に示しています。型アサーションの書き方には「as構文」と「アングルブラケット構文」がありますが、以下のサンプルコードでは「as構文」を用いています。

interface Test {
  foo: number;
}

let test = {} as Test; // 型アサーションにいってtestの型をTest型に上書き
test.foo = 123; // コンパイルエラーが発生しなくなる

上記のサンプルコードのように型アサーションを用いることによって、コンパイルエラーが発生しなくなります。

なお、「アングルブラケット構文(<>表記)」を用いる場合には、以下に示すように記述します。

interface Test {
  foo: number;
}

let test = <Test>{}; // 型アサーションにいってtestの型をTest型に上書き
test.foo = 123; // コンパイルエラーが発生しなくなる

「as構文」と「アングルブラケット構文」のどちらを用いるかは好みですが、以下に示すようにアングルブラケット構文」は「JSX構文」と区別がつかないことがあるため、「as構文」を用いる方がおすすめです。

interface Test {
  foo: number;
}

let test = <Test>{}; // 型アサーションにいってtestの型をTest型に上書き
</Test>
// 「アングルブラケット構文」か「JSX構文」なのか分かりにくい

型アサーション(as)が使えない場合

型アサーションを用いると型推論を上書きできますが、無茶な型の変換はできないので注意してください。例えば、string型をnumber型にする型アサーションはコンパイルエラーになります。

以下に型アサーションが使えない場合のサンプルコードを示しています。

let foo: number = 'aaa' as number;
// 以下のコンパイラエラーが発生
// Conversion of type 'string' to type 'number' may be a mistake
// because neither type sufficiently overlaps with the other.
// If this was intentional, convert the expression to 'unknown' first.
// (型 'string' から型 'number' への変換は、
// 互いに十分に重複できないため間違っている可能性があります。
// 意図的にそうする場合は、まず式を 'unknown' に変換してください。)

上記のサンプルコードをコンパイルしようとすると、コンパイルエラーが発生します。発生したコンパイルエラーを要約すると、「string型をnumber型に変換するのは間違いです。お互いの型に重複している部分が少ないです」と書いています。

なお、型アサーションを意図的にしている場合(自分の書いた型アサーションの方が正しい場合など)は、以下に示すようにunknown型やany型を経由することでコンパイルエラーの発生を防ぐことができます。

// unknown型を経由
let foo: number = 'aaa' as unknown as number;
// any型を経由
let foo: number = 'aaa' as any as number;

「型アサーション」と「型アノテーション(型注釈)」の違い

型アサーションを用いると、型を上書きすることができます。一方、型アノテーションを用いると、コンパイラに変数の型を明示することができます。どちらも似たような機能ですが、型アサーションを用いると以下に示すような危険性があるので注意してください。

  • プロパティの追加を忘れてもコンパイラが指摘してくれない
  • 実行時にエラーが発生する可能性がある

プロパティの追加を忘れてもコンパイラが指摘してくれない

以下のサンプルコードを見てみましょう。型アノテーションを用いると、fooプロパティがないため、コンパイルエラーが発生しますが、型アサーションを用いると、コンパイルエラーが発生しません。そのため、必要なプロパティを実際に追加するのを忘れてしまう危険性があります。

interface Test {
  foo: number;
}

// 型アサーションを用いた場合には何も指摘されない
let test01 = {} as Test;

// 型アノテーションを用いた場合にはfooプロパティがないと指摘される(コンパイルエラー発生)
let test02: Test = {};
// 以下のコンパイルエラーが発生
// Property 'foo' is missing in type '{}' but required in type 'Test'
// (プロパティ 'foo' は型 '{}' にありませんが、型 'Test' では必須です。)

実行時にエラーが発生する可能性がある

型アサーションではunknown型やany型を経由することで無茶な型変換が可能になります。そのため、以下のコードではコンパイルエラーは発生しませんが、string型のfoonumber型のbarに代入しているので、実行時にエラーが発生してしまう危険性があります。

let foo: string = 'Test';
let bar: number = foo as unknown as number;
// string型のfooをnumber型のbarに代入している

型アサーションの使い方

開発者がコンパイラよりも型についてよく知っている時に型アサーションをよく使います。以下のサンプルコードを見てみましょう。

const input = document.getElementById('myInput') as HTMLInputElement;
console.log(input.value); // 入力値を取得

コンパイラはWebページ上のDOMに対して権限を持たないので、上記のサンプルコードにおいて、コンパイラはmyInputという要素があるかどうか、myInputがインプット要素であるかどうかは分かりません。そのため、document.getElementById('myInput')HTMLElementを返すことをコンパイラはデフォルトで想定します。

一方、開発者はmyInputの要素がHTMLInputElementであることを知っています。このような場合には、開発者がコンパイラよりも型についてよく知っているので、型アサーションを用いてコンパイラに型を教えてあげる必要があります。これにより、開発者はinput.valueといったHTMLInputElement特有のプロパティに安全にアクセスすることができるようになります。

本記事のまとめ

この記事ではTypeScriptの『型アサーション(as)』について、以下の内容を説明しました。

  • 型アサーション(as)とは
  • 「型アサーション」と「型アノテーション(型注釈)」の違い
  • 型アサーションの使い方

お読み頂きありがとうございました。