Тихий ужас каждого React-разработчика: event.target.value и TypeScript
Вы когда-нибудь писали обработчик onChange для простого инпута, а TypeScript вдруг начинал кричать красным? Знакомо: "Property 'value' does not exist on type 'EventTarget'". Этот вопрос на Stack Overflow собрал десятки тысяч просмотров, и не зря. Кажется, что может быть проще — взять значение из поля ввода? Но дьявол, как всегда, в деталях.
В 2025 году экосистема TypeScript стала ещё строже. Новые версии компилятора лучше анализируют типы, и старые "хаки" перестали работать. Если вы пишете на React или просто используете TypeScript для работы с DOM, эта статья — ваша спасательная шлюпка. Мы разберём не только как типизировать onChange, но и почему это работает именно так, а не иначе. Поехали!
Почему event.target.value — это боль?
Давайте сразу к делу. Вот типичный пример, который вызывает ошибку:
const handleChange = (event: any) => {
const value = event.target.value; // Работает, но это "any" — зло
};
Проблема в том, что event может быть чем угодно. TypeScript не знает, что это за элемент и есть ли у него свойство value. Более того, использование any убивает всю магию статической типизации. Вы теряете автодополнение и защиту от ошибок.
Правильный путь — использовать конкретные типы. Но какие? Давайте разбираться.
Лучшие практики типизации onChange в 2025 году
1. React: используем ChangeEvent
Если вы работаете с React, забудьте про нативный Event. React использует свои синтетические события — SyntheticEvent. Для инпутов, текстэриа и селектов идеально подходит ChangeEvent.
import React, { ChangeEvent } from 'react';
const MyInput = () => {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
console.log(value); // value — строка, TypeScript доволен
};
return <input type="text" onChange={handleChange} />;
};
Почему это работает? ChangeEvent<HTMLInputElement> — это дженерик, который точно знает, что event.target — это HTMLInputElement. А у него есть свойство value типа string. Всё логично и типобезопасно.
2. Для textarea и select
Аналогичный подход работает для других элементов форм. Просто меняйте дженерик:
ChangeEvent<HTMLTextAreaElement>— для<textarea>ChangeEvent<HTMLSelectElement>— для<select>ChangeEvent<HTMLInputElement>— для<input>
const handleTextareaChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
const text = event.target.value; // string
};
const handleSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
const selectedValue = event.target.value; // string
};
3. Чистый TypeScript (без React): Event + type assertion
Если вы не используете React, а работаете напрямую с DOM, всё немного иначе. Нативный Event не знает о свойстве value. Вам нужно привести event.target к правильному типу.
const inputElement = document.getElementById('my-input') as HTMLInputElement;
inputElement.addEventListener('change', (event: Event) => {
const target = event.target as HTMLInputElement;
const value = target.value;
console.log(value);
});
Важно: Используйте приведение типов (as HTMLInputElement) только тогда, когда вы на 100% уверены в типе элемента. Иначе рискуете получить ошибку в рантайме.
4. Универсальный хендлер: когда нужно переиспользовать
Часто бывает, что один и тот же обработчик используется для разных типов полей. Например, форма с текстом и числом. В 2025 году лучший способ — использовать дженерики или объединение типов.
type InputChangeHandler = (event: ChangeEvent<HTMLInputElement>) => void;
const handleChange: InputChangeHandler = (event) => {
const value = event.target.value;
// Логика
};
// Или для нескольких типов:
const handleAnyInput = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = event.target.value;
};
Продвинутые техники: типизация value для разных типов input
Не все инпуты одинаковы. type="checkbox" использует checked, а не value. type="number" возвращает строку, хотя ждёшь число. Давайте разберём частные случаи.
Checkbox: ловим checked
const handleCheckboxChange = (event: ChangeEvent<HTMLInputElement>) => {
const isChecked = event.target.checked; // boolean
// Никогда не используйте event.target.value для checkbox!
};
Number input: преобразуем строку в число
const handleNumberChange = (event: ChangeEvent<HTMLInputElement>) => {
const rawValue = event.target.value; // строка, например "42"
const numericValue = Number(rawValue); // число 42
// Или используйте event.target.valueAsNumber (если браузер поддерживает)
const valueAsNumber = event.target.valueAsNumber; // number или NaN
};
File input: работаем с файлами
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const files = event.target.files; // FileList | null
if (files) {
const firstFile = files[0]; // File | undefined
}
};
Частые ошибки и как их избежать
- Ошибка: Использование
EventвместоChangeEvent.
Решение: Всегда импортируйтеChangeEventиз React. - Ошибка: Забыли указать дженерик для элемента.
Решение: ПишитеChangeEvent<HTMLInputElement>, а не простоChangeEvent. - Ошибка: Использование
event.target.valueдля кастомных компонентов.
Решение: Типизируйте пропсы кастомного компонента отдельно, не полагайтесь на нативные события. - Ошибка: Приведение типа без проверки.
Решение: Используйтеinstanceofили type guards, если не уверены в типе элемента.
TypeScript 5.x и будущее: что изменилось в 2025