TL;DR

内容

型アノテーションを優先する理由

型アノテーションは、値がその型に適合するかをチェックする。一方、型アサーションはチェックしない。

interface Person {
  name: string
}
 
const alice: Person = {} // 型エラー
const bob = {} as Person // OK

余剰プロパティチェックも、型アノテーションのときにしか行われない。

const alice: Person = { name: "Alice", occupation: "TypeScript developer" } // 型エラー
const bob = { name: "Bob", occupation: "JavaScript developer" } as Person // OK

アロー関数で型アノテーションを使う

次のコードでは、people の型は { name: string }[] と推論され、Person[] にはならない。

const people = ["alice", "bob", "jan"].map((name) => ({ name }))

アロー関数の中で型アノテーション付きの変数を宣言することもできるが、ノイズが多くなる。代わりに、アロー関数の戻り値に型アノテーションを付ければよい(項目24)。

const people = ["alice", "bob", "jan"].map((name): Person => ({ name })) // Person[]

型アサーションを使うべき場面

型アサーションは、値の型について TypeScript が知らない情報をプログラマが知っているときだけ使う。典型的な例は DOM 要素の型である。

document.querySelector("#myButton")?.addEventListener("click", (e) => {
  e.currentTarget // EventTarget | null
  const button = e.currentTarget as HTMLButtonElement // HTMLButtonElement
})

非 null アサーション(!)も同じである。ただし、多くの場合はオプショナルチェーン(?.)を使うとよい。

const el = document.getElementById("foo")! // HTMLElement
document.getElementById("foo")?.addEventListener("click", () => alert("Hi there!"))

型アサーションや非 null アサーションを使うときは、なぜ必要なのかを説明するコメントを書く。

型アサーションの限界

型アサーションは、2 つの型が互いに「比較可能」なとき、つまり積集合が空でないとき(一方が他方のサブタイプであるときなど)にしかできない。HTMLElement は HTMLElement | null のサブタイプであり、Person は {} のサブタイプなので、型アサーションできる。一方、Person と HTMLElement の積集合は空(never)なので、型アサーションできない。ただし、一度 unknown にアサーションすれば通せる(項目46)。

const body = document.body
const el = body as Person // 型エラー
const el2 = body as unknown as Person // OK

as を使わない型アサーション

すべての型アサーションが as を使うわけではない。ユーザー定義型ガード(is)は、型アサーションを何らかのロジックと結び付け、その正しさをチェックする(項目22)。ジェネリクスを使う方法もあるが、TypeScript が型をチェックしていないのに、チェックしているように見えるので避ける(項目51)。

型アサーションはキャストではない

C 言語などのキャストは、実行時に値を変換する。一方、型アサーションは型空間の構文であり、実行時には消えるので値を変えない。型アサーションは、値についてすでに真であることを断言するためのものである。なお、as const は安全である(項目24)。

訳者による解説

型アサーションは危険なのに、型エラーを簡単に消せる、any より罪悪感なく使える、といった理由で安易に使われがちである。安易に頼らず、型エラーの原因を突き止めて解消し、型安全なコードを書くべきである。ただし、型アサーションが有効な場面もある。

キーワード