TL;DR

  • DOM の型は EventTarget、Node、Element、HTMLElement の順に階層になっていて、TypeScript ではこの階層の理解がより重要になる。
  • getElementById のように汎用的な型が返るときは、型アサーションか instanceof による実行時チェックで具体的な型にする。

内容

DOM の型階層

DOM の型は、たとえば次のような包含関係になっている。

型例
EventTargetwindow、XMLHttpRequest
Nodedocument、Text、Comment
ElementHTMLElement、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 などの具体的なイベントがある。

キーワード