#15 - Типи для праці з DOM, типізація евентів, браузерне API в TypeScript

Якщо ви думали, що під типізацію підпадають тільки дефолтні змінні, класи, функції, то ні - типізується все, включно з HTML DOM елементами.  Коли ви використовуєте DOM звернення, то ви також повинні вказати тип цього елементу.

const input = document.querySelector('input') // тип: HTMLInputElement | null

const input = document.querySelector<HTMLInputElement>('input')
if (input) {
  input.value = 'Привіт';
}

const img = document.querySelector<HTMLImageElement>('img')
if (!img) return;
img.src = 'logo.png'

const el = document.querySelector<HTMLElement>('#my-element')
const id = el?.dataset.id
const attr = el?.getAttribute('data-role')

В TypeScript існують абсоютно всі типи для всіх існуючих елементів. Перераховувати їх всі я не має сенсу, тому що їх назва залежить від самого елементу, наприклад:

  • <div> - HTMLDivElement
  • <form> - HTMLFormElement
  • <canvas> - HTMLCanvasElement
  • <a> - HTMLAnchorElement
  • <img> - HTMLImageElement
  • <button> - HTMLButtonElement

Всі ці назви, це по факту аналог mem типів, які були в JS ще задовго до TS, і оскільки ми зараз маємо справу з динамічними фреймворками, типізація HTML це на сама часто-викорустовуєма річ, але якщо ви будете це використовувати, або бачити в коді, то знайте, що всі ці типи можно легко знайти в енциклопедії.

Типізація евентів

Як ви знаєте в DOM, окрім самих елементів, є ще евенти, які вішаються на елементи, їх потрібно також типізувати:

const button = document.querySelector<HTMLButtonElement>('button')

button?.addEventListener('click', (event: MouseEvent) => {
  console.log(event.clientX, event.clientY)
})

Як і в випадку з DOM елементами, поді є дуже багато, я покажу приклад, всю ж бібліотеку ви зможете знайти в енциклопедії.

  • click, mousedown, mouseup - MouseEvent
  • input, change - InputEvent
  • submit - SubmitEvent
  • keydown, keyup, keypress - KeyboardEvent
  • focus, blur - FocusEvent
  • touchstart, touchend - TouchEvent

Чесно кажучи типізовувати евенти це якась дикість, тому що тип евенту ви і так вказуєте в самому евенті. Я просто не думаю, що хтось може не зрозуміти, що addEventListener('keypress') відноситься не до клавіатури... Тем не менш, деякі компанії, хочуть типізувати все і у них є така можливість.

const form = document.querySelector<HTMLFormElement>('form')

form?.addEventListener('submit', (e: SubmitEvent) => {
  e.preventDefault()
  const target = e.target as HTMLFormElement
  const formData = new FormData(target)
  console.log(formData.get('email'))
})

Типізація браузерного API

Жесть з якою ви можете зіткнутися - це браузерне API. Проблема в тому, що воно постійно розширюється. Деякі методи мають підтримку із коробки, інші треба розширювати плагінами, інші взагалі не підтримуються, і не зрозуміло чи будуть. Проблема в тому, що дізнатися це ви можете тільки методом проб та помилок, тому що документація ТС не поспіває за оновленнями браузеру.

Хороший приклад вбудованих типів, це localStorage, він автомтично типізується в string або null:

localStorage.setItem('theme', 'dark')
const theme = localStorage.getItem('theme') // string | null

if ('geolocation' in navigator) {
  navigator.geolocation.getCurrentPosition((pos) => {
    console.log(pos.coords.latitude) // теж саме
  })
}

В fetch тип ви вже вказуєте самостійно:

fetch('/api/user')
  .then(res => res.json())
  .then((data: { name: string; age: number }) => {
    console.log(data.name)
  })

В таких випадках вам допоможе тільки інтуіція та інтернет з актуальною інфою.

Під кінець ще хотів би показати кастомні події, ящо ви зіткалися з складими проектами, то я думаю ви вже знаєте що це таке, але як же їх типізувати, приблизно так:

const event = new CustomEvent('user-logged-in', {
  detail: { id: 123 }
})
document.dispatchEvent(event)

document.addEventListener('user-logged-in', (e: CustomEvent<{ id: number }>) => {
  console.log(e.detail.id)
})