Avatar ·

Обработка события onChange в TypeScript: как получить event.target.value

Привет! Я новичок в TypeScript и пытаюсь разобраться с обработкой событий в React. У меня есть простой инпут, и я хочу получать его значение при каждом изменении. Но TypeScript ругается на тип события. Вот мой код: ```tsx const handleChange = (e) => { console.log(e.target.value); }; return ; ``` TypeScript выдаёт ошибку: `Parameter 'e' implicitly has an 'any' type`. Я попробовал добавить тип `React.ChangeEvent`, но всё равно не работает: ```tsx const handleChange = (e: React.ChangeEvent) => { console.log(e.target.value); }; ``` Теперь ошибка: `Property 'value' does not exist on type 'EventTarget'`. Я гуглил, но везде разные советы. Кто-то пишет про `React.ChangeEvent`, кто-то советует делать `(e.target as HTMLInputElement).value`. Подскажите, как правильно типизировать событие onChange в TypeScript для React? Хочется понять не только "как", но и "почему" так нужно делать. Заранее спасибо! <<>>

Правильное типизирование события onChange в React + TypeScript

Проблема, с которой вы столкнулись, очень распространена среди разработчиков, переходящих на TypeScript. Давайте разберёмся по шагам.

Почему возникает ошибка?

В TypeScript React.ChangeEvent — это обобщённый (generic) тип, который ожидает конкретный HTML-элемент. Когда вы пишете просто React.ChangeEvent, TypeScript не знает, с каким именно элементом вы работаете. По умолчанию event.target имеет тип EventTarget, у которого нет свойства value — оно есть только у конкретных элементов, например, HTMLInputElement.

Правильное решение

Используйте React.ChangeEvent:

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

return <input type="text" onChange={handleChange} />;

Теперь TypeScript знает, что e.target — это HTMLInputElement, у которого есть свойство value. Всё работает без ошибок.

Альтернативный подход (деструктуризация)

Можно сразу извлечь значение из события:

const handleChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
  console.log(value);
};

Этот способ более лаконичный, но требует понимания деструктуризации.

Что делать, если элемент динамический?

Иногда вы не знаете заранее тип элемента (например, при работе с textarea или select). В таких случаях:

  • Для <textarea> — React.ChangeEvent<HTMLTextAreaElement>
  • Для <select> — React.ChangeEvent<HTMLSelectElement>
  • Для универсального случая — используйте приведение типа: (e.target as HTMLInputElement).value, но это менее предпочтительно.

Почему не работает приведение через as?

Некоторые разработчики пишут:

const handleChange = (e: React.ChangeEvent) => {
  const value = (e.target as HTMLInputElement).value;
};

Это работает, но это "костыль". Вы теряете проверку типов — если передадите это событие для <div>, TypeScript не предупредит об ошибке. Используйте generic-типы — это правильный путь.

Полный пример с формой

import React, { useState } from 'react';

const MyForm: React.FC = () => {
  const [name, setName] = useState('');

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setName(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Submitted:', name);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={name} onChange={handleChange} />
      <button type="submit">Submit</button>
    </form>
  );
};

Теперь у вас есть полностью типизированная форма. TypeScript будет проверять, что вы используете правильные свойства и методы.

<<>>

Использование обобщённой функции-обработчика

Если у вас много разных инпутов и вы не хотите писать отдельный обработчик для каждого, можно создать универсальную функцию с использованием generics:

const createChangeHandler = <T extends HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
  setter: React.Dispatch<React.SetStateAction<string>>
) => {
  return (e: React.ChangeEvent<T>) => {
    setter(e.target.value);
  };
};

// Использование:
const [name, setName] = useState('');
const handleNameChange = createChangeHandler(setName);

return <input type="text" onChange={handleNameChange} />;

Работа с несколькими полями формы

Для форм с множеством полей удобно использовать один обработчик с name атрибутом:

interface FormState {
  name: string;
  email: string;
}

const [form, setForm] = useState<FormState>({ name: '', email: '' });

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;
  setForm(prev => ({ ...prev, [name]: value }));
};

return (
  <>
    <input name="name" value={form.name} onChange={handleChange} />
    <input name="email" value={form.email} onChange={handleChange} />
  </>
);

Этот подход масштабируется на любое количество полей и сохраняет строгую типизацию.