TL;DR
- DOM の型は
EventTarget、Node、Element、HTMLElementの順に階層になっていて、TypeScript ではこの階層の理解がより重要になる。 getElementByIdのように汎用的な型が返るときは、型アサーションかinstanceofによる実行時チェックで具体的な型にする。
内容
DOM の型階層
DOM の型は、たとえば次のような包含関係になっている。
| 型 | 例 |
|---|---|
| EventTarget | window、XMLHttpRequest |
| Node | document、Text、Comment |
| Element | HTMLElement、SVGElement |
| HTMLElement | <i>、<b> |
| HTMLButtonElement | <button> |
これらは TypeScript の型であるだけでなく、JavaScript の実行時の値でもある。
EventTarget: DOM の型の最上位にある。イベントリスナの追加と削除、イベントのディスパッチしかできないNode: テキストやコメント、Elementを含む。childNodesプロパティは子のNodeをNodeListで返すElement:SVGElement、HTMLElement、MathMLElementなど、すべての要素オブジェクトを含む。childrenプロパティは子のElementをHTMLCollectionで返すHTMLElement: HTML のElementで、HTMLImageElementやHTMLInputElementなど任意の HTML 要素を含む
HTMLCollection は名前に反して、HTMLElement だけを含むわけではない。MDN は次のように説明している。
このインターフェイスは歴史的な理由で HTMLCollection と呼ばれています(現代の DOM より前では、このインターフェイスを実装する集合が、アイテムとして HTML 要素しか持てませんでした)。
TypeScript で具体的な型を得る
TypeScript の DOM の型定義は、具体的な型を得られるようにリテラル型を多用している。
const p = document.getElementsByTagName("p")[0]
// ^? const p: HTMLParagraphElement
const button = document.createElement("button")
// ^? const button: HTMLButtonElement
const div = document.querySelector("div")
// ^? const div: HTMLDivElement | nullしかし、getElementById などは HTMLElement 型を返す。
const div = document.getElementById("my-div")
// ^? const div: HTMLElement | null#my-div が div であると事前に分かっているなら、型アサーションを使えばよい(項目9)。
const div = document.getElementById("my-div") as HTMLDivElement分からない場合は、実行時にチェックする。
const div = document.getElementById("my-div")
if (div instanceof HTMLDivElement) {
console.log(div)
// ^? const div: HTMLDivElement
}Event の型階層
イベントの型の最上位は Event で、その下に UIEvent、MouseEvent、TouchEvent、KeyboardEvent などの具体的なイベントがある。