Работа с DOM

Работа с DOM

Технический фундамент DOM-типизации

DOM-типизация в TypeScript строится вокруг двух рисков:

  • элемент может отсутствовать (null);
  • элемент может быть не того класса (Element vs HTMLInputElement).

Поэтому безопасный паттерн почти всегда такой:

  1. получить элемент;
  2. сузить тип через проверку (if, instanceof);
  3. только потом вызывать специфичные свойства/методы.

Эта дисциплина резко снижает рантайм-падения из-за изменений верстки.

Почему TypeScript особенно полезен в DOM-коде

DOM-код часто ломается из-за двух типичных причин: элемент не найден (null) или найден элемент не того типа. В JavaScript это обычно всплывает в рантайме. TypeScript заставляет тебя учесть эти сценарии заранее.

Ключевой момент: в TypeScript доступ к DOM строится через строгие типы элементов и проверки на null.

Проверь себя: почему вызов button.addEventListener(...) без проверки может упасть на проде?

Типы у querySelector

querySelector возвращает Element | null, потому что элемент может отсутствовать.

const button = document.querySelector('#save-btn');

if (button) {
  button.addEventListener('click', () => {
    console.log('save');
  });
}

Смотри, что важно: проверка if (button) это narrowing, после нее TypeScript понимает, что button не null.

Уточнение типа элемента

Иногда нужен конкретный тип (HTMLInputElement, HTMLButtonElement и т.д.).

const input = document.querySelector('#email') as HTMLInputElement | null;

if (input) {
  console.log(input.value);
}

Если ты уверен в ожидаемом типе элемента, можно подсказать его через generic-параметр (и не писать as).

const input = document.querySelector<HTMLInputElement>('#email');

if (input) {
  console.log(input.value);
}

Смотри, что важно: generic не убирает необходимость проверки на null и не делает проверку типа в рантайме. Если нет 100% уверенности, безопаснее сузить через instanceof.

Лучше использовать безопасные проверки, а не слепое приведение, если не уверен в структуре DOM.

const el = document.querySelector('#email');
if (el instanceof HTMLInputElement) {
  console.log(el.value);
}

События и типы

Обработчики событий имеют типизированный объект события.

const form = document.querySelector('#signup-form');

if (form) {
  form.addEventListener('submit', (event) => {
    event.preventDefault();
  });
}

Если нужен доступ к target, часто требуется дополнительное сужение типа.

Смотри, что важно: event.target это элемент, по которому кликнули/ввели (часто вложенный), а event.currentTarget это элемент, на котором висит обработчик. В типах оба начинаются как EventTarget | null, поэтому обычно делают narrowing через instanceof.

const input = document.querySelector<HTMLInputElement>('#email');

if (input) {
  input.addEventListener('input', (event) => {
    const el = event.currentTarget;
    if (el instanceof HTMLInputElement) {
      console.log(el.value);
    }
  });
}

Проверь себя: почему event.target не всегда имеет свойства value без проверки?

Атрибуты, dataset и приведение

dataset возвращает строки или undefined, это важно учитывать.

const card = document.querySelector('[data-id]');
if (card instanceof HTMLElement) {
  const id = card.dataset.id; // string | undefined
}

Здесь часто путаются: даже если атрибут обычно есть, TypeScript требует учитывать случай отсутствия.

Реальные микро-сценарии

  1. Форма логина.

Типы гарантируют, что ты работаешь именно с HTMLInputElement, а не абстрактным Element.

  1. Кнопка отправки.

Проверка null защищает от падения на страницах, где кнопка не отрендерилась.

  1. Список карточек.

Типизированный dataset помогает безопасно извлекать id перед API-запросом.

Частые ошибки новичков

  • Использовать ! (non-null assertion) везде без проверки.
  • Делать слепое as HTMLInputElement без уверенности.
  • Игнорировать, что querySelector может вернуть null.
  • Считать, что event.target всегда нужного типа.

Анти-провал: если элемент критичен, проверяй его существование и тип сразу после выбора.

Что будет, если изменить входные данные

Если селектор #email больше не существует в шаблоне, код с проверкой if (input) безопасно пропустит логику. Код без проверки упадет на чтении input.value. Именно такие "мелкие" изменения в верстке часто ломают JS-логику без типовой дисциплины.

Проверь себя: когда уместно использовать !, и почему это должно быть исключением, а не правилом?

Краткий итог

  • DOM-API в TypeScript требует учитывать null и точные типы элементов.
  • Безопасный DOM-код строится через narrowing (if, instanceof).
  • События и target часто требуют дополнительного уточнения типов.
  • Слепые приведения и non-null assertions повышают риск рантайм-багов.
  • TypeScript в DOM особенно полезен для устойчивости UI при изменениях верстки.