TypeScript の DOM の型とは、TypeScript に同梱されている lib.dom.d.ts というファイルに書かれた、要素やイベントの型定義のことです。 input 要素なら HTMLInputElement、click イベントなら PointerEvent というように、タグ名とイベント名ごとに型が決まっています。

この記事では、タグ名 112 件と要素の型、イベント名 108 件とイベントの型の対応表を、TypeScript 7.0.2 に入っている lib.dom.d.ts から機械的に取り出して載せました。あわせて、querySelector の戻り値の型、null の扱い方 5 通り、event.target の型エラーを、tsc の出力全文と Chromium での実行結果つきで整理します。

確認環境:Ubuntu 24.04 / TypeScript 7.0.2(npx tsc --version の表示は Version 7.0.2)/ Node.js 22.22.2 / Playwright 1.56.0 と Chromium 141.0.7390.37。

DOMの型の早見表

やりたいこと 書き方 得られる型
タグ名で1つ取る document.querySelector("input") HTMLInputElement | null
id やクラスで1つ取る document.querySelector("#name") Element | null
型を指定して1つ取る document.querySelector<HTMLInputElement>("#name") HTMLInputElement | null
タグ名で全部取る document.querySelectorAll("li") NodeListOf<HTMLLIElement>
クラスで全部取る document.querySelectorAll(".item") NodeListOf<Element>
id で取る document.getElementById("name") HTMLElement | null
要素を作る document.createElement("canvas") HTMLCanvasElement
クリックを受け取る addEventListener("click", (e) => …) e は PointerEvent
入力を受け取る addEventListener("input", (e) => …) e は InputEvent
キー入力を受け取る addEventListener("keydown", (e) => …) e は KeyboardEvent
フォーム送信を受け取る addEventListener("submit", (e) => …) e は SubmitEvent
イベントの発生元 e.target / e.currentTarget どちらも EventTarget | null
入力欄の値を読む if (e.target instanceof HTMLInputElement) の中で e.target.value string
よく出るエラー 意味 直し方
TS2531: Object is possibly 'null'. 式の結果が null かもしれない if、?.、instanceof で確かめる
TS18047: 'input' is possibly 'null'. 変数が null かもしれない 同上
TS2339: Property 'value' does not exist on type 'Element'. 型が広すぎて value が見えない instanceof かタグ名のセレクタで絞る

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TypeScript DOMの型一覧 要素・イベントの型早見表」

DOMの型はどこで決まっているか

DOM の型は、TypeScript 本体と一緒に入る lib.dom.d.ts に書かれています。tsconfig.json の lib に "dom" を入れると読み込まれます。この記事の tsc の出力は、すべて次の設定で取りました。

{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "lib": ["es2022", "dom", "dom.iterable"],
    "strict": true,
    "rootDir": "src",
    "noEmit": true
  },
  "include": ["src"]
}

要素の型は、次のように親から子へ受け継がれています。下に行くほど使えるプロパティが増えるので、「value が無い」と言われたら、いま持っている型がどの段にいるかを見ると原因がわかります。

graph TD
  A[EventTarget] --> B[Node]
  B --> C[Element]
  C --> D[HTMLElement]
  C --> E[SVGElement]
  D --> F[HTMLInputElement]
  D --> G[HTMLButtonElement]
  D --> H[HTMLDivElement など]
型 主に持っているもの この型になる場面
EventTarget addEventListener、dispatchEvent e.target、e.currentTarget
Node textContent、parentNode、childNodes childNodes の中身
Element id、classList、getAttribute、querySelector クラスや id で querySelector したとき
HTMLElement style、dataset、hidden、focus、click getElementById、section や span など
HTMLInputElement など value、checked、files、disabled タグ名で取ったとき、instanceof で絞ったあと

タグ名から要素の型を引く一覧(112件)

document.querySelector("タグ名") や document.createElement("タグ名") が返す型は、lib.dom.d.ts の HTMLElementTagNameMap という対応表で決まります。TypeScript 7.0.2 の中身を取り出すと、タグ名は 112 件、型は 63 種類でした。型ごとにまとめた一覧が次の表です。

タグ名 要素の型
a HTMLAnchorElement
abbr address article aside b bdi bdo cite code dd dfn dt em figcaption figure footer header hgroup i kbd main mark nav noscript rp rt ruby s samp search section small strong sub summary sup u var wbr HTMLElement
area HTMLAreaElement
audio HTMLAudioElement
base HTMLBaseElement
blockquote q HTMLQuoteElement
body HTMLBodyElement
br HTMLBRElement
button HTMLButtonElement
canvas HTMLCanvasElement
caption HTMLTableCaptionElement
col colgroup HTMLTableColElement
data HTMLDataElement
datalist HTMLDataListElement
del ins HTMLModElement
details HTMLDetailsElement
dialog HTMLDialogElement
div HTMLDivElement
dl HTMLDListElement
embed HTMLEmbedElement
fieldset HTMLFieldSetElement
form HTMLFormElement
h1 h2 h3 h4 h5 h6 HTMLHeadingElement
head HTMLHeadElement
hr HTMLHRElement
html HTMLHtmlElement
iframe HTMLIFrameElement
img HTMLImageElement
input HTMLInputElement
label HTMLLabelElement
legend HTMLLegendElement
li HTMLLIElement
link HTMLLinkElement
map HTMLMapElement
menu HTMLMenuElement
meta HTMLMetaElement
meter HTMLMeterElement
object HTMLObjectElement
ol HTMLOListElement
optgroup HTMLOptGroupElement
option HTMLOptionElement
output HTMLOutputElement
p HTMLParagraphElement
picture HTMLPictureElement
pre HTMLPreElement
progress HTMLProgressElement
script HTMLScriptElement
select HTMLSelectElement
slot HTMLSlotElement
source HTMLSourceElement
span HTMLSpanElement
style HTMLStyleElement
table HTMLTableElement
tbody tfoot thead HTMLTableSectionElement
td th HTMLTableCellElement
template HTMLTemplateElement
textarea HTMLTextAreaElement
time HTMLTimeElement
title HTMLTitleElement
tr HTMLTableRowElement
track HTMLTrackElement
ul HTMLUListElement
video HTMLVideoElement

専用の型を持たないタグ(section、header、strong など 39 件)は、すべて HTMLElement になります。HTMLElement にあるプロパティだけで足りるためです。

同じファイルには、ほかにも対応表があります。

対応表の名前 件数 例
HTMLElementTagNameMap 112 input は HTMLInputElement
SVGElementTagNameMap 63 svg は SVGSVGElement、circle は SVGCircleElement
MathMLElementTagNameMap 31 数式用の要素
HTMLElementDeprecatedTagNameMap 29 font は HTMLFontElement、marquee は HTMLMarqueeElement

一覧を自分の環境で取り出すスクリプト

TypeScript の版が変わると一覧も変わります。手元の版で確かめたいときは、次の Python スクリプトを typescript を入れたフォルダで実行します。

import re, sys

path = "node_modules/@typescript/typescript-linux-x64/lib/lib.dom.d.ts"
src = open(path, encoding="utf-8").read()

def members(name):
    body = re.search(r"^interface " + name + r"\b[^{]*\{\n(.*?)^\}", src, re.S | re.M).group(1)
    return re.findall(r'^\s+"([^"]+)": ([^;]+);', body, re.M)

tags = members("HTMLElementTagNameMap")
events = members("ElementEventMap") + members("GlobalEventHandlersEventMap")
print("タグ:", len(tags), "件 / 型:", len({t for _, t in tags}), "種類")
print("イベント:", len(events), "件 / 型:", len({t for _, t in events}), "種類")
for name, type_ in (tags if "tags" in sys.argv else events if "events" in sys.argv else []):
    print(name, "->", type_)
$ python3 list-dom-types.py
タグ: 112 件 / 型: 63 種類
イベント: 108 件 / 型: 20 種類
$ python3 list-dom-types.py tags
…(省略)…
a -> HTMLAnchorElement
abbr -> HTMLElement
address -> HTMLElement
area -> HTMLAreaElement
…(省略)…

path は、TypeScript 7.0.2 を Linux(x64)に入れたときの場所です。見つからないときは find node_modules -name lib.dom.d.ts で探して書き換えます。

要素を取得するメソッドごとの戻り値の型

同じ要素を取る場合でも、書き方によって戻り値の型が変わります。次のコードを tsc の --declaration で型定義ファイルに出力し、推論された型を確かめました。

export const a = document.querySelector("input");
export const b = document.querySelector("#name");
export const c = document.querySelector("input.name");
export const d = document.querySelector<HTMLInputElement>("#name");
export const e = document.querySelector("#name") as HTMLInputElement | null;
export const f = document.querySelectorAll("li");
export const g = document.querySelectorAll(".item");
export const h = document.querySelectorAll<HTMLLIElement>(".item");
export const i = document.getElementById("name");
export const j = document.getElementsByClassName("item");
export const k = document.getElementsByTagName("li");
export const l = document.createElement("canvas");
export const m = document.createElement("my-box");
export const n = document.body.closest("form");
export const o = document.body.closest(".card");
export const p = document.querySelector("svg");
export const q = document.querySelector("circle");
export const r = document.body.children;
export const s = document.body.firstElementChild;
export const t = document.body.parentElement;
export const u = document.body.childNodes;
export const v = document.forms;
export const w = document.activeElement;

出力された型定義(npx tsc -p . --noEmit false --declaration --emitDeclarationOnly --outDir dts)です。

export declare const a: HTMLInputElement | null;
export declare const b: Element | null;
export declare const c: Element | null;
export declare const d: HTMLInputElement | null;
export declare const e: HTMLInputElement | null;
export declare const f: NodeListOf<HTMLLIElement>;
export declare const g: NodeListOf<Element>;
export declare const h: NodeListOf<HTMLLIElement>;
export declare const i: HTMLElement | null;
export declare const j: HTMLCollectionOf<Element>;
export declare const k: HTMLCollectionOf<HTMLLIElement>;
export declare const l: HTMLCanvasElement;
export declare const m: HTMLElement;
export declare const n: HTMLFormElement | null;
export declare const o: Element | null;
export declare const p: SVGSVGElement | null;
export declare const q: SVGCircleElement | null;
export declare const r: HTMLCollection;
export declare const s: Element | null;
export declare const t: HTMLElement | null;
export declare const u: NodeListOf<ChildNode>;
export declare const v: HTMLCollectionOf<HTMLFormElement>;
export declare const w: Element | null;

ここから読み取れるポイントは4つです。

  • セレクタがタグ名そのもの(a、f、n、p)のときだけ、専用の型になる。 "input.name" のようにクラスを足した c は Element | null になります。文字列がタグ名と完全に一致するかどうかで決まるためです。
  • 1つ取るメソッドは | null が付き、全部取るメソッドは付かない。 見つからないとき、前者は null、後者は空のリストを返します。
  • getElementById は HTMLElement、querySelector は Element から始まります。style や dataset は HTMLElement にあるので、document.querySelector(".card").style は null を除いても TS2339 になります。
  • createElement は null が付かない。 対応表にない名前(m)は HTMLElement になります。

d のジェネリクスと e の as は、どちらも「この型だと信じる」という指定です。実際の要素を調べているわけではない点は、後半の Chromium の結果で確かめます。

nullかもしれないエラーの全文と5通りの直し方

まず、エラーになる例です。

document.querySelector("input").value = "taro";

const input = document.querySelector("input");
input.value = "taro";

const name1 = document.querySelector("#name");
if (name1) {
  name1.value = "taro";
}
$ npx tsc -p .
src/null1.ts(1,1): error TS2531: Object is possibly 'null'.
src/null1.ts(4,1): error TS18047: 'input' is possibly 'null'.
src/null1.ts(8,9): error TS2339: Property 'value' does not exist on type 'Element'.

--locale ja を付けると日本語になります。

$ npx tsc -p . --locale ja
src/null1.ts(1,1): error TS2531: オブジェクトは 'null' である可能性があります。
src/null1.ts(4,1): error TS18047: 'input' は 'null' の可能性があります。
src/null1.ts(8,9): error TS2339: プロパティ 'value' は型 'Element' に存在しません。
番号 出る場面 原因
TS2531 メソッドの戻り値にそのまま .value と続けた 式に名前がないので「Object」と表示される
TS18047 変数に入れてから使った 変数名つきで表示される。原因は TS2531 と同じ
TS2339 null は除いたが、型が Element のまま value は HTMLInputElement などにしかない

TS2531 と TS18047 は同じ原因で、書き方によって番号が変わるだけです。3つ目の TS2339 は null とは別の話で、型を絞る必要があります。

次のコードは、5通りの直し方を1つのファイルにまとめたものです。このまま npx tsc -p . が通ります。

// 1. if で確かめる(タグ名で取ったので型は HTMLInputElement | null)
const a = document.querySelector("input");
if (a) {
  a.value = "1: if";
}

// 2. オプショナルチェーン ?.(読むだけ・呼ぶだけのとき)
const len = document.querySelector("input")?.value.length;
document.querySelector("button")?.focus();

// 3. instanceof(null でないことと、要素の種類を同時に確かめる)
const c = document.querySelector("#name");
if (c instanceof HTMLInputElement) {
  c.value = "3: instanceof";
}

// 4. 見つからなければ止める関数(何度も使うならこれ)
function mustGet<T extends Element>(selector: string, type: new () => T): T {
  const el = document.querySelector(selector);
  if (!(el instanceof type)) {
    throw new Error(`${selector} が ${type.name} として見つかりません`);
  }
  return el;
}
const d = mustGet("#name", HTMLInputElement);
d.value = "4: mustGet";

// 5. 非 null アサーション !(必ず在ると言い切れる場合だけ)
const e = document.querySelector("input")!;
e.value = "5: !";

console.log(len, a?.value);
方法 null のとき 型も絞れるか 向いている場面
1. if 中の処理を飛ばす タグ名で取った場合のみ その場で1回使う
2. ?. undefined になる 絞れない 値を読む、メソッドを呼ぶ。代入の左辺に書くと TS2779 になる
3. instanceof 中の処理を飛ばす 絞れる id やクラスで取った要素、e.target
4. 止める関数 原因のわかるエラーで止まる 絞れる 画面に必ずあるはずの要素を何度も取る
5. ! そのまま進み、次の行で TypeError 絞れない 直前に自分で作った要素など、存在が確かなとき

迷ったら 3 の instanceof を選ぶと、null と型の両方を1行で確かめられます。画面の土台になる要素には 4 が便利で、HTML 側の id を変えたときに、どのセレクタが見つからないのかがメッセージでわかります。

イベント名からイベントの型を引く一覧(108件)

要素.addEventListener("イベント名", (e) => …) の e の型は、HTMLElementEventMap で決まります。この表は ElementEventMap(2件)と GlobalEventHandlersEventMap(106件)を受け継いでいて、合わせて 108 件、型は 20 種類です。型ごとにまとめました。

イベントの型 件数 イベント名
Event 43 beforematch cancel canplay canplaythrough change close command contextlost contextrestored cuechange durationchange emptied ended fullscreenchange fullscreenerror invalid load loadeddata loadedmetadata loadstart pause play playing pointerrawupdate ratechange reset scroll scrollend seeked seeking select selectionchange selectstart slotchange stalled suspend timeupdate volumechange waiting webkitanimationend webkitanimationiteration webkitanimationstart webkittransitionend
PointerEvent 13 auxclick click contextmenu gotpointercapture lostpointercapture pointercancel pointerdown pointerenter pointerleave pointermove pointerout pointerover pointerup
MouseEvent 8 dblclick mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup
DragEvent 7 drag dragend dragenter dragleave dragover dragstart drop
AnimationEvent 4 animationcancel animationend animationiteration animationstart
FocusEvent 4 blur focus focusin focusout
TouchEvent 4 touchcancel touchend touchmove touchstart
TransitionEvent 4 transitioncancel transitionend transitionrun transitionstart
ClipboardEvent 3 copy cut paste
CompositionEvent 3 compositionend compositionstart compositionupdate
KeyboardEvent 3 keydown keypress keyup
InputEvent 2 beforeinput input
ToggleEvent 2 beforetoggle toggle
UIEvent 2 abort resize
ErrorEvent 1 error
FormDataEvent 1 formdata
ProgressEvent 1 progress
SecurityPolicyViolationEvent 1 securitypolicyviolation
SubmitEvent 1 submit
WheelEvent 1 wheel

window と document には、上の 108 件に加えて専用のイベントがあります。

対象 対応表 追加されるイベントの例
document DocumentEventMap DOMContentLoaded visibilitychange readystatechange(いずれも Event)
window WindowEventMap beforeunload(BeforeUnloadEvent)、hashchange(HashChangeEvent)、popstate(PopStateEvent)、storage(StorageEvent)、message(MessageEvent)、unhandledrejection(PromiseRejectionEvent)、online offline(Event)
video 要素 HTMLVideoElementEventMap enterpictureinpicture leavepictureinpicture(PictureInPictureEvent)

よく使うイベントの型と、そこから読めるプロパティです(型は tsc 7.0.2 で確かめたもの)。

型 主なプロパティと型
PointerEvent clientX: number、button: number、pointerType: string
MouseEvent clientX: number、clientY: number
KeyboardEvent key: string、code: string、isComposing: boolean
InputEvent data: string | null、inputType: string
SubmitEvent submitter: HTMLElement | null
FocusEvent relatedTarget: EventTarget | null
WheelEvent deltaY: number
DragEvent dataTransfer: DataTransfer | null
ClipboardEvent clipboardData: DataTransfer | null
TouchEvent touches: TouchList
ToggleEvent newState: string

押さえておきたい点が3つあります。

  • click は PointerEvent、dblclick は MouseEvent です。PointerEvent は MouseEvent を受け継いでいるので、clientX などはどちらでも使えます。
  • input は InputEvent、change は Event です。change の e には data や inputType がありません。
  • 対応表にない名前(独自のイベントなど)は Event になります。

イベントハンドラの関数を別に定義するときの型

関数を外に出すときは、引数に表の型を書きます。イベント名と型が合わないと TS2769 になります。

const field = document.createElement("input");
function onKey(e: KeyboardEvent) { console.log(e.key); }
field.addEventListener("click", onKey);
src/ev_err.ts(14,33): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '(e: KeyboardEvent) => void' is not assignable to parameter of type 'EventListenerOrEventListenerObject'.
      Type '(e: KeyboardEvent) => void' is not assignable to type 'EventListener'.
        Types of parameters 'e' and 'evt' are incompatible.
          Type 'Event' is missing the following properties from type 'KeyboardEvent': altKey, charCode, code, ctrlKey, and 17 more.

(行番号は、次の章のエラー例と同じファイルに続けて書いたときのものです。)"click" を "keydown" に直すと通ります。onclick などのプロパティに代入する場合の型は次のとおりです。

export declare const oc: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; // button.onclick
export declare const oi: ((this: GlobalEventHandlers, ev: InputEvent) => any) | null;   // input.oninput
export declare const ok: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null; // input.onkeydown

event.targetとcurrentTargetの型とvalueの読み方

イベントの型がわかっても、e.target と e.currentTarget の型はどちらも EventTarget | null です。そのまま value を読むとエラーが2つずつ出ます。

const field = document.createElement("input");
field.addEventListener("input", (e) => {
  console.log(e.target.value);
});
field.addEventListener("input", (e) => {
  console.log(e.currentTarget.value);
});
field.addEventListener("change", (e) => {
  if (e.target) {
    console.log(e.target.value);
  }
});
src/ev_err.ts(3,15): error TS18047: 'e.target' is possibly 'null'.
src/ev_err.ts(3,24): error TS2339: Property 'value' does not exist on type 'EventTarget'.
src/ev_err.ts(6,15): error TS18047: 'e.currentTarget' is possibly 'null'.
src/ev_err.ts(6,31): error TS2339: Property 'value' does not exist on type 'EventTarget'.
src/ev_err.ts(10,26): error TS2339: Property 'value' does not exist on type 'EventTarget'.

if で null を除いても、EventTarget には value がないので TS2339 が残ります。直し方は3つあります。

const field = document.createElement("input");

// 直し方1:instanceof で絞る
field.addEventListener("input", (e) => {
  if (e.target instanceof HTMLInputElement) {
    console.log(e.target.value);
  }
});

// 直し方2:登録した要素の変数をそのまま使う
field.addEventListener("input", () => {
  console.log(field.value);
});

// 直し方3:function で書いて this を使う(this は登録した要素の型になる)
field.addEventListener("input", function () {
  console.log(this.value);
});

型は同じでも、実行時に入っている要素は違います。target は実際に操作された要素、currentTarget はリスナーを登録した要素です。ボタンの中の span をクリックした場合の違いは、次の章の実行結果で確かめます。

e.target e.currentTarget function の this
型 EventTarget | null EventTarget | null 登録した要素の型
実行時の中身 実際に操作された要素(子要素のこともある) リスナーを登録した要素 リスナーを登録した要素
向いている場面 親にまとめて登録して、どの子が押されたか調べる 登録した要素そのものを扱う アロー関数でない場合に型つきで扱う

instanceofの絞り込みをChromiumで確かめる

型の上での絞り込みが、実行時にも合っているかを確かめます。次の HTML と TypeScript を用意し、tsc で JavaScript に変換して Chromium で動かしました。

<!doctype html>
<meta charset="utf-8">
<input id="name" value="taro">
<select id="plan"><option>A</option><option>B</option></select>
<textarea id="memo">memo</textarea>
<div id="box">box</div>
<button type="button"><span>送信</span></button>
const log: string[] = [];

// (1) instanceof による絞り込み
for (const sel of ["#name", "#plan", "#memo", "#box", "#none"]) {
  const el = document.querySelector(sel); // Element | null
  if (el instanceof HTMLInputElement) {
    log.push(`${sel}: HTMLInputElement value=${el.value} type=${el.type}`);
  } else if (el instanceof HTMLSelectElement) {
    log.push(`${sel}: HTMLSelectElement value=${el.value} options=${el.options.length}`);
  } else if (el instanceof HTMLElement) {
    log.push(`${sel}: HTMLElement (${el.constructor.name}) tagName=${el.tagName}`);
  } else {
    log.push(`${sel}: ${el}`);
  }
}

// (2) ジェネリクスは実行時には何も確かめない
const wrong = document.querySelector<HTMLInputElement>("#box");
if (wrong) {
  log.push(`generic: constructor=${wrong.constructor.name} value=${wrong.value}`);
  try {
    wrong.value.trim();
  } catch (err) {
    log.push(`generic: ${err}`);
  }
}

// (3) target と currentTarget
const button = document.querySelector("button")!;
button.addEventListener("click", (e) => {
  const t = e.target;
  const c = e.currentTarget;
  log.push(`event: ${e.constructor.name}`);
  log.push(`target: ${t instanceof HTMLElement ? t.tagName : t}`);
  log.push(`currentTarget: ${c instanceof HTMLElement ? c.tagName : c}`);
  log.push(`target is button: ${t instanceof HTMLButtonElement}`);
  log.push(`currentTarget is button: ${c instanceof HTMLButtonElement}`);
});
document.querySelector<HTMLElement>("button span")!.click();

// (4) input イベント
const nameInput = document.querySelector("input")!;
nameInput.addEventListener("input", (e) => {
  log.push(`input event: ${e.constructor.name}`);
  if (e.target instanceof HTMLInputElement) {
    log.push(`input value: ${e.target.value}`);
  }
});
(window as any).__log = log;

入力欄に hanako と入力したあとの log の中身です(Chromium 141.0.7390.37)。

#name: HTMLInputElement value=taro type=text
#plan: HTMLSelectElement value=A options=2
#memo: HTMLElement (HTMLTextAreaElement) tagName=TEXTAREA
#box: HTMLElement (HTMLDivElement) tagName=DIV
#none: null
generic: constructor=HTMLDivElement value=undefined
generic: TypeError: Cannot read properties of undefined (reading 'trim')
event: PointerEvent
target: SPAN
currentTarget: BUTTON
target is button: false
currentTarget is button: true
input event: InputEvent
input value: hanako

結果からわかることです。

  • (1) instanceof は実行時に要素の種類を調べるので、型と中身が必ず一致する。 5つのセレクタが、型の上での分岐どおりに振り分けられました。
  • (2) ジェネリクスで HTMLInputElement と指定しても、実際は HTMLDivElement のまま。 tsc は通りますが、value は undefined で、trim を呼んだところで TypeError になりました。ジェネリクスや as は、HTML を自分で管理していて要素の種類が確かな場合に使い、それ以外は instanceof を選ぶと安心です。
  • (3) span をクリックすると、target は SPAN、currentTarget は BUTTON。 型は同じ EventTarget | null でも中身は別の要素でした。イベントの型は、一覧表のとおり PointerEvent です。
  • (4) input イベントは InputEvent で、instanceof で絞ったあとは value を string として読めました。

フォームや属性まわりでよく出る型

要素とイベント以外で、null や undefined が付く型をまとめます(tsc 7.0.2 で確かめたもの)。

書き方 型 扱い方
form.elements.namedItem("name") Element | RadioNodeList | null instanceof HTMLInputElement で絞る
new FormData(form).get("name") FormDataEntryValue | null typeof v === "string" で絞る
el.dataset.id string | undefined ?? "" で既定値を与える
el.getAttribute("href") string | null ?? "" か if
el.classList DOMTokenList そのまま add、remove、toggle
el.style CSSStyleDeclaration el が HTMLElement のときだけ使える
el.parentElement HTMLElement | null if か ?.
el.firstElementChild Element | null instanceof で絞る

よくある質問

Q. HTMLElement と Element はどう使い分けますか? A. style、dataset、focus、click を使うなら HTMLElement、id や classList、getAttribute だけなら Element で足ります。関数の引数は、必要なプロパティを持つ中でいちばん広い型にすると、渡せる要素が増えます。

Q. e.target as HTMLInputElement と書いてもよいですか? A. tsc は通ります。ただし as は実行時に何も調べないので、子要素から伝わってきたイベントでは別の要素が入っていることがあります。e.target instanceof HTMLInputElement なら、型と中身の両方を1行で確かめられます。

Q. querySelectorAll の結果で map が使えません。 A. 戻り値の NodeListOf は配列ではないため、forEach と for...of は使えますが map や filter はありません。Array.from(document.querySelectorAll("li")) か [...document.querySelectorAll("li")] で配列にします。for...of やスプレッドを使うには、tsconfig.json の lib に "dom.iterable" を入れておきます。

Q. document や window が見つからないというエラーが出ます。 A. tsconfig.json の lib に "dom" が入っているかを確かめます。lib を自分で指定すると、書いたものだけが読み込まれます。

まとめ

  • DOM の型は lib.dom.d.ts にあり、タグ名 112 件・イベント名 108 件の対応表から決まる(TypeScript 7.0.2)
  • querySelector はセレクタがタグ名そのもののときだけ専用の型になり、それ以外は Element | null
  • TS2531 と TS18047 は「null かもしれない」、TS2339 は「型が広すぎる」。前者は if や ?.、後者は instanceof で直す
  • instanceof は null と要素の種類を同時に確かめられ、実行時の中身とも一致する
  • ジェネリクスと as は型を言い切るだけ。div に HTMLInputElement を指定すると、tsc は通っても実行時に TypeError になる
  • click は PointerEvent、input は InputEvent、change は Event、submit は SubmitEvent
  • e.target と e.currentTarget の型はどちらも EventTarget | null。中身は「操作された要素」と「登録した要素」で異なる

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「TypeScript DOMの型一覧 要素・イベントの型早見表」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?