Типизация Event Handlers

Типизация обработчиков событий в TypeScript — указание правильного типа объекта события (MouseEvent, KeyboardEvent, InputEvent) в параметре callback, чтобы TypeScript знал доступные свойства события и цели.

Зачем нужно

event.target в обработчиках событий имеет тип EventTarget | null, а не конкретный DOM-элемент. Без правильной типизации нельзя обратиться к input.value или button.disabled без кастов. TypeScript предоставляет точные типы для каждого вида события.

Где используется

  • Обработчики DOM-событий: click, input, submit, keydown
  • React event handlers: onChange, onClick, onSubmit
  • Custom events через CustomEvent<T>
  • WebSocket, EventSource события
  • Drag-and-drop события

Основной контент

Встроенные типы событий

// Основные типы событий DOM
const btn = document.querySelector("button")!;

btn.addEventListener("click", (e: MouseEvent) => {
  console.log(e.clientX, e.clientY); // координаты клика
  console.log(e.target);             // EventTarget | null
  console.log(e.currentTarget);      // EventTarget | null
});

document.addEventListener("keydown", (e: KeyboardEvent) => {
  console.log(e.key, e.code);  // "Enter", "Enter"
  console.log(e.ctrlKey);      // boolean
});

const input = document.querySelector("input")!;
input.addEventListener("input", (e: InputEvent) => {
  const target = e.target as HTMLInputElement;
  console.log(target.value); // string
});

Типизация target элемента

// e.target — EventTarget | null — нет value, checked и т.д.
// Нужен cast к конкретному типу элемента

function handleInput(e: Event): void {
  const target = e.target as HTMLInputElement;
  console.log(target.value); // OK после cast
}

// Лучше: использовать HTMLElementEventMap
function handleChange(e: Event): void {
  if (e.target instanceof HTMLInputElement) {
    console.log(e.target.value); // string — без cast, через narrowing
  }
  if (e.target instanceof HTMLSelectElement) {
    console.log(e.target.value); // string
    console.log(e.target.selectedIndex); // number
  }
}

React event handlers

import { MouseEvent, ChangeEvent, FormEvent, KeyboardEvent } from "react";

// Клик по кнопке
function handleClick(e: MouseEvent<HTMLButtonElement>): void {
  e.preventDefault();
  console.log(e.currentTarget.disabled); // HTMLButtonElement
}

// Изменение input
function handleChange(e: ChangeEvent<HTMLInputElement>): void {
  console.log(e.target.value);   // string
  console.log(e.target.checked); // boolean (для checkbox)
}

// Изменение select
function handleSelect(e: ChangeEvent<HTMLSelectElement>): void {
  console.log(e.target.value);        // string
  console.log(e.target.selectedIndex); // number
}

// Submit формы
function handleSubmit(e: FormEvent<HTMLFormElement>): void {
  e.preventDefault();
  const form = e.currentTarget;
  // form: HTMLFormElement
}

// Keyboard event
function handleKeyDown(e: KeyboardEvent<HTMLInputElement>): void {
  if (e.key === "Enter") {
    console.log(e.currentTarget.value);
  }
}

Custom Events

// Создание custom event с типизированными данными
interface OrderPlacedEventDetail {
  orderId: string;
  total: number;
  items: string;
}

// Отправка
const event = new CustomEvent<OrderPlacedEventDetail>("orderPlaced", {
  detail: { orderId: "o-1", total: 99.99, items: ["item-1"] },
  bubbles: true,
});
document.dispatchEvent(event);

// Получение
document.addEventListener("orderPlaced", (e: Event) => {
  const custom = e as CustomEvent<OrderPlacedEventDetail>;
  console.log(custom.detail.orderId); // string
  console.log(custom.detail.total);   // number
});

EventEmitter-паттерн

type EventMap = {
  click:  MouseEvent;
  input:  InputEvent;
  submit: SubmitEvent;
};

type Handler<K extends keyof EventMap> = (event: EventMap[K]) => void;

function addHandler<K extends keyof EventMap>(
  element: HTMLElement,
  type: K,
  handler: Handler<K>
): void {
  element.addEventListener(type, handler as EventListener);
}

Частые ошибки

  • Использовать Event вместо конкретного типаEvent не имеет clientX, key и т.д.; используйте MouseEvent, KeyboardEvent.
  • Не проверять e.target перед доступомe.target это EventTarget | null; без instanceof-проверки нельзя обратиться к value.
  • React: путать Event (DOM) и React SyntheticEvent — React оборачивает события; импортируйте типы из react, не из lib.dom.d.ts.
  • Не вызывать e.preventDefault() — для форм и ссылок без этого произойдёт стандартное действие браузера.

Связанные темы

Ресурсы