TypeScript HTML要素取得時の型引数指定と型アサーション(キャストのようなもの)

HTML要素をdocument.querySelectorAll()で取得してforEachかけ、要素の属性値を確認しようとしたところ

プロパティ 'dataset' は型 'Element' に存在しません。

と怒られた。Element?

HTMLElementにキャストとかすればいいのか、と思って検索すると、型アサーションの書き方がひっかかる。
アサーションってなんだろうと思ってたけどキャスト的なやつだったのか。

Type Assertion(型アサーション) - TypeScript Deep Dive 日本語版

手を加えたらエラーが消えたが…

document.querySelectorAll("[data-msgbox]").forEach(($el)=>{
  // const msg = $el.dataset.msgbox; // プロパティ 'dataset' は型 'Element' に存在しません。
  // const msg = (<HTMLElement>$el).dataset.msgbox; // 型アサーション
  const msg = ($el as HTMLElement).dataset.msgbox; // 同上
  console.log('msg',msg);
});

要素の取得メソッドに型引数を指定する

さらに調べてみるともっと良い書き方があった。

querySelector, querySelectorAll等のメソッドを実行する際、
あらかじめメソッドに型引数を指定するのが良いらしい。

TypeScript で querySelector メソッドを使うときに型引数を指定する - Hatena Developer Blog

これはいけません! これだと Element | null 型から HTMLElement 型にキャストすることになり、nullability が除去されてしまいます。このままコードを書き続けると、要素が見つからないときに実行時エラーを引き起こしかねません。

divの取得で確認してみたところ、確かに要素取得メソッド実行後の戻り値に型アサーションをかけると、結果がnullになることを考慮しない書き方となってしまう。(nullability という表現覚えました)
要素取得メソッドに型引数を渡した場合は型が自動的に HTMLElement | null となり、nullになる可能性を示してくれる。

const $div1 = document.querySelector("div") // const $div1: HTMLDivElement | null
const $div2 = document.querySelector("div") as HTMLElement //const $div2: HTMLElement
const $div3 = document.querySelector<HTMLElement>("div") // const $div3: HTMLElement | null

最初にあげた例の書き方としては以下がよりよい

document.querySelectorAll<HTMLElement>("[data-msgbox]").forEach(($el)=>{
  const msg = $el.dataset.msgbox;
  console.log('msg',msg);
});