«Property 'value' does not exist on type 'EventTarget'» — как перестать бояться и начать типизировать события в TypeScript
Вы когда-нибудь писали обработчик события в TypeScript, пытались получить event.target.value — и тут же получали красное подчёркивание и сообщение: «Property 'value' does not exist on type 'EventTarget'»? Если да — вы не одиноки. На Stack Overflow этот вопрос собрал десятки тысяч просмотров, и он продолжает мучать новичков и опытных разработчиков в 2025 году.
Проблема не в том, что TypeScript «злой» или «сломанный». Всё наоборот: компилятор защищает вас от глупых ошибок, которые могли бы упасть в рантайме. Давайте разберёмся, почему возникает эта ошибка и как её правильно чинить.
Почему TypeScript ругается на event.target.value?
Краткий ответ: event.target имеет тип EventTarget | null. А у EventTarget нет свойства value. У него есть только три метода: addEventListener, removeEventListener и dispatchEvent.
Когда вы пишете:
const handleChange = (event: React.ChangeEvent) => {
const value = event.target.value; // ❌ Ошибка!
};
TypeScript видит: «Эй, я не знаю, какой конкретно HTML-элемент вызвал это событие. Может быть, это <div>, у которого нет value, а может быть <input> — я не уверен. Поэтому я отказываюсь компилировать такой код».
Это фича, а не баг. TypeScript заставляет вас явно указать, с каким элементом вы работаете.
Топ-3 способа решения (актуально для 2025)
Есть несколько подходов. Мы рассмотрим самые популярные и безопасные.
1. Каст через as HTMLInputElement (быстро, но осторожно)
Самый распространённый способ — привести event.target к нужному типу:
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = (event.target as HTMLInputElement).value;
console.log(value);
};
Плюсы: просто, понятно, работает.
Минусы: если вы ошиблись и на самом деле событие пришло от <textarea> или <select>, то вы получите undefined в рантайме. Компилятор вас не предупредит.
Лучше использовать этот метод, когда вы абсолютно уверены в типе элемента. Например, в простых формах с одним полем.
2. Использовать event.currentTarget вместо event.target
В TypeScript currentTarget типизирован более строго, чем target. Если вы укажете generic-параметр для события, то currentTarget будет иметь правильный тип:
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.value; // ✅ Работает без каста!
};
Важно: currentTarget — это элемент, на который подписан обработчик. Если вы подписались на <input>, то currentTarget всегда будет этим <input>. В отличие от target, который может быть вложенным элементом (например, <span> внутри кнопки).
Плюсы: безопаснее, чем каст, не нужно писать лишних приведений.
Минусы: подходит не для всех сценариев — например, для событий всплытия (bubbling) currentTarget может не совпадать с целевым элементом.
3. Дженерик-функция с проверкой (самый безопасный способ)
Если вы хотите максимальной безопасности и переиспользуемости, напишите вспомогательную функцию, которая проверяет тип элемента:
function getInputValue(event: Event): string | null {
const target = event.target;
if (target instanceof HTMLInputElement) {
return target.value;
}
if (target instanceof HTMLTextAreaElement) {
return target.value;
}
return null;
}
// Использование
const handleChange = (event: Event) => {
const value = getInputValue(event);
if (value !== null) {
console.log(value);
}
};
Плюсы: абсолютная типобезопасность, работает с любыми событиями (не только React), легко тестировать.
Минусы: больше кода, нужно писать вспомогательные функции.
Этот подход особенно полезен, если вы работаете с нативными DOM-событиями, а не с синтетическими событиями React.
А что насчёт React 19 и новых типов в 2025?
В 2025 году TypeScript и React продолжают развиваться. В React 19 улучшили типизацию событий, но проблема с event.target остаётся — это фундаментальное ограничение DOM API. TypeScript не может знать, какой элемент вызвал событие, пока вы ему не скажете.
Однако появилась новая тенденция: использовать strict event handlers с автоматическим выводом типа currentTarget. Многие современные библиотеки (например, MUI, Chakra UI) уже экспортируют строго типизированные обработчики. Рекомендуем следовать их примеру.
Практический чек-лист: как избежать ошибки в будущем
- Всегда типизируйте аргумент события. Вместо
(event) => ...пишите(event: React.ChangeEvent<HTMLInputElement>) => ... - Используйте
currentTargetвместоtarget— это уменьшает количество кастов. - Если нужен
target— делайте проверку черезinstanceofперед тем, как обращаться к.value. - Выносите логику проверки в отдельные функции-хелперы — это улучшает читаемость и переиспользование.
- Не злоупотребляйте
as any— это убивает всю пользу от TypeScript.
Заключение: действуйте осознанно
Ошибка «Property 'value' does not exist on type 'EventTarget'» — это не баг TypeScript, а его суперсила. Компилятор не даёт вам написать код, который может упасть в рантайме. Ваша задача — помочь ему, явно указав тип элемента.
В 2025 году лучшие практики просты:
- Для простых случаев — используйте
currentTargetс generic'ом. - Для сложных или нативных событий — пишите защитные проверки с
instanceof.