TypeScript input onChange: как правильно ловить event.target.value в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Тихий ужас каждого 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

Блоги

Рекомендаций по книгам