События и обработка событий в TypeScript
Дальше: TypeScript и React · Ветвления · События в JavaScript
В браузере UI строится на событиях (click, input, submit). TypeScript задаёт конкретный тип события — доступны правильные поля (value, clientX, preventDefault). В React используются синтетические обёртки — 21.md.
Маршрут: синтаксис → события → TypeScript и React.
Подробная модель DOM и bubbling — события в JavaScript. Таблицы глобалов — Справочник — DOM и окружение.
Окружение и глобальные типы
TypeScript знает браузерные API через lib в tsconfig:
{
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
| Окружение | Типичный lib / пакет |
|---|---|
| Браузер | DOM, DOM.Iterable |
| Node.js | @types/node (без DOM) |
| Тесты (jsdom) | DOM + настройки test runner |
Глобалы вроде document, window, fetch появляются из lib, а не из вашего кода. Для своих глобалов (версия сборки, фичефлаги) — declare const в global.d.ts — 9.md §декларации.
Node-окружение (process, Buffer) типизируется через @types/node — 22.md.
Базовый обработчик
const button = document.querySelector("button");
if (!(button instanceof HTMLButtonElement)) {
throw new Error("Button not found");
}
button.addEventListener("click", (e: MouseEvent) => {
console.log(e.clientX, e.clientY);
});
Разбор:
querySelectorвозвращаетElement | null— сужайте черезinstanceof— 12.md.- Для
clickна кнопке —MouseEvent, не общийEvent.
Типы DOM-событий
| Событие | Тип |
|---|---|
click на элементе | MouseEvent |
input / change на <input> | Event или InputEvent |
input на <input type="text"> | лучше через target |
submit на <form> | SubmitEvent |
keydown | KeyboardEvent |
const input = document.querySelector("#email");
if (!(input instanceof HTMLInputElement)) throw new Error("no input");
input.addEventListener("input", () => {
console.log(input.value);
});
const form = document.querySelector("form");
if (!(form instanceof HTMLFormElement)) throw new Error("no form");
form.addEventListener("submit", (e: SubmitEvent) => {
e.preventDefault();
const data = new FormData(form);
console.log(data.get("email"));
});
Разбор:
- В
inputlistenerinput.valueуже типизирован послеinstanceof. - Альтернатива:
(e.target as HTMLInputElement).value— слабее.
currentTarget vs target
list.addEventListener("click", (e: MouseEvent) => {
const target = e.target;
if (!(target instanceof HTMLElement)) return;
const item = target.closest("[data-id]");
if (!item) return;
const id = item.getAttribute("data-id");
console.log("clicked", id);
});
Разбор:
- Делегирование: один listener на списке, клик по дочерним элементам.
closestищет предка с атрибутом.
Клавиатура
window.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === "s") {
e.preventDefault();
console.log("Save shortcut");
}
});
Пользовательские события
type OrderCreatedDetail = { orderId: string };
const event = new CustomEvent<OrderCreatedDetail>("order:created", {
detail: { orderId: "ord-1" },
bubbles: true,
});
window.addEventListener("order:created", (e: Event) => {
if (!(e instanceof CustomEvent)) return;
const detail = e.detail as OrderCreatedDetail;
console.log(detail.orderId);
});
window.dispatchEvent(event);
Разбор:
- Generic у
CustomEvent<T>зависит от lib DOM; иногдаdetailтипизируют вручную после проверки. - Typed emitter в приложении — 28.md.
React: кратко
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
События React не нативные MouseEvent — обёртки с похожим API. Не смешивайте addEventListener и React на одном узле без причины.
Частые ошибки
| Ошибка | Что делать |
|---|---|
(e: Event) везде | конкретный тип |
as HTMLInputElement без проверки | instanceof |
Забыли preventDefault на submit | перезагрузка страницы |
onclick в HTML | addEventListener в TS-модуле |
Практика
- Форма:
input,submit, вывод ошибок валидации. - Горячая клавиша
Ctrl+SсKeyboardEvent. CustomEventсdetailи подписчик.- Делегирование клика по списку
data-id. - Тот же сценарий в React — 21.md.