Skip to content

Item 75: Understand the DOM Hierarchy

要点

  • The DOM has a type hierarchy that you can usually ignore while writing JavaScript. But these types become more important in TypeScript. Understanding them will help you write TypeScript for the browser.
  • Know the differences between Node, Element, HTMLElement, and EventTarget, as well as those between Event and MouseEvent.
  • Either use a specific enough type for DOM elements and Events in your code or give TypeScript the context to infer it.

正文

ts
function handleDrag(eDown: Event) {
  const targetEl = eDown.currentTarget
  targetEl.classList.add('dragging')
  // ~~~~~           'targetEl' is possibly 'null'
  //       ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
  const dragStart = [
    eDown.clientX,
    eDown.clientY,
    //    ~~~~~~~        ~~~~~~~ Property '...' does not exist on 'Event'
  ]
  const handleUp = (eUp: Event) => {
    targetEl.classList.remove('dragging')
    // ~~~~~           'targetEl' is possibly 'null'
    //       ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
    targetEl.removeEventListener('mouseup', handleUp)
    // ~~~~~ 'targetEl' is possibly 'null'
    const dragEnd = [
      eUp.clientX,
      eUp.clientY,
      //  ~~~~~~~      ~~~~~~~   Property '...' does not exist on 'Event'
    ]
    console.log(
      'dx, dy = ',
      [0, 1].map((i) => dragEnd[i] - dragStart[i])
    )
  }
  targetEl.addEventListener('mouseup', handleUp)
  // ~~~~~ 'targetEl' is possibly 'null'
}

const surfaceEl = document.getElementById('surface')
surfaceEl.addEventListener('mousedown', handleDrag)
// ~~~~~~ 'surfaceEl' is possibly 'null'

💻 playground


ts
function handleDrag(eDown: Event) {
  const targetEl = eDown.currentTarget
  targetEl.classList.add('dragging')
  // ~~~~~           'targetEl' is possibly 'null'
  //       ~~~~~~~~~ Property 'classList' does not exist on type 'EventTarget'
  // ...
}

💻 playground


ts
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

💻 playground


ts
const div = document.getElementById('my-div')
//    ^? const div: HTMLElement | null

💻 playground


ts
document.getElementById('my-div') as HTMLDivElement

💻 playground


ts
const div = document.getElementById('my-div')
if (div instanceof HTMLDivElement) {
  console.log(div)
  //          ^? const div: HTMLDivElement
}

💻 playground


ts
const div = document.getElementById('my-div')!
//    ^? const div: HTMLElement

💻 playground


ts
function handleDrag(eDown: Event) {
  // ...
  const dragStart = [
    eDown.clientX,
    eDown.clientY,
    //    ~~~~~~~        ~~~~~~~ Property '...' does not exist on 'Event'
  ]
  // ...
}

💻 playground


ts
function addDragHandler(el: HTMLElement) {
  el.addEventListener('mousedown', (eDown) => {
    const dragStart = [eDown.clientX, eDown.clientY]
    const handleUp = (eUp: MouseEvent) => {
      el.classList.remove('dragging')
      el.removeEventListener('mouseup', handleUp)
      const dragEnd = [eUp.clientX, eUp.clientY]
      console.log(
        'dx, dy = ',
        [0, 1].map((i) => dragEnd[i] - dragStart[i])
      )
    }
    el.addEventListener('mouseup', handleUp)
  })
}

const surfaceEl = document.getElementById('surface')
if (surfaceEl) {
  addDragHandler(surfaceEl)
}

💻 playground

Released under the MIT License.