TypeScriptでのDOM取得型付けとNullの扱い

2021-04-29

概要

TypeScriptでDOMを操作する際に、取得したDOMの型付けは避けて通れません。
そのままではエラーが出たり、Nullの可能性もありますので、nullチェックをする必要があります。

ここでは、DOM操作の型付けについて考えてみます。

一つの要素の取得

DOM操作をする際はdocumentオブジェクトからセレクタを指定するところから始まります。 例えばnav要素を取得する方法は以下の通り。

<nav class="nav">
  <ul>
    <li><a class="link" href="#">test01</a></li>
    <li><a class="link" href="#">test02</a></li>
    <li><a class="link" href="#">test03</a></li>
    <li><a class="link" href="#">test04</a></li>
  </ul>
</nav>
// セレクタ指定はHTMLElement
const navSelectorElement = document.querySelector('nav') // HTMLElement | null
// クラス指定はElement
const navClassElement = document.querySelector('.nav') // Element | null

取得の方法によって型推論が違うのがわかります。
このままではいくつかのエラーが出てしまいます。

Nullの可能性を示唆するエラー

まずNullのチェックをする必要があります。
例えばstyleプロパティにアクセスするときに以下のエラーが出ます。

const nav = document.querySelector('.nav') 
nav.style.color = 'red' // Object is possibly 'null'

この場合、nav変数がnullではないことを明示する必要があります。

Element型でのエラー

HTMLElement型はElement型からプロパティを継承されています。
それゆえにElement型だけだとアクセスできないプロパティがあります。

例えばstyle操作です。

const nav = document.querySelector('.nav')
nav.style.color = 'red' // Property 'style' does not exist on type 'Element'

エラーの通りElementにはstyleプロパティがない、とあります。

エラー回避

一つずつ処理しましょう。

まずはnullですが、以下の方法があります。

nav?.style.color = 'red' // optional chaining
nav!.style.color = 'red' // Exclamation
if (nav) nav.style.color = 'red' // if

ちなみにoptional chainingを使用するとThe left-hand side of an assignment expression may not be an optional property access.とエラーが出ます。
代入式の左辺でoptional chainingを使用したアクセスであってはならないということです。
この場合はifを使用しましょう。というか最初からifを使用した方が安全だと思われます。

次にElementの型にHTMLElementを伝える必要があります。

const nav = document.querySelector('.nav') as HTMLElement // HTMLElement
const nav = document.querySelector<HTMLElement>('.nav') // HTMLElement | Null

型アサーションを使用すると、確かに指定の型は伝わっていますがnullが抜けました。
これでは安全とは言えませんね。

なので2番目の方法で、ジェネリクスを利用した方法がnullを損なわずにすみます。

複数要素の取得

こちらも同様にジェネリクスを利用します。

const links = document.querySelectorAll<HTMLElement>('.link') // NodeListOf<HTMLElement>

もしくは、処理の中で以下のようにインスタンスを継承しているかをチェックするのも良いです。というかこちらの方が確実で良いかなと思います。

const links = document.querySelectorAll('.link')
links.forEach((link) => {
  link.addEventListener('click', (event) => {
    event.preventDefault()
    // HTMLAnchorElementから生成されたオブジェクトとする
    if (link instanceof HTMLAnchorElement) {
      // if節の中では引数linkはHTMLAnchorElementとなる
      link.style.color = 'red'
      console.log(`click!! ${link.textContent}`)
    }
  })
})

結論

型を意識せざるを得ない状態にもってくると、より理解が深まるように思います。
今何のオブジェクトの型を扱っていて、どのプロパティが使えるのかを意識しつつコーディングしていきたいですね。

運営について

Natural Tearoomはシステム開発会社フロントエンドエンジニアがんちゃんが運営するメディアです。
フロントエンド技術を中心に発信しています。

· プライバシーポリシー

SNS

© 2021 天然珈琲店