Avatar ·

Типизация события onChange в React и TypeScript: как получить event.target.value

Всем привет! Я новичок в TypeScript, перехожу с обычного JavaScript на React + TypeScript. Столкнулся с проблемой при обработке события onChange в инпуте. Не могу понять, как правильно типизировать event. Вот мой код: ```tsx import React, { useState } from 'react'; const MyForm: React.FC = () => { const [value, setValue] = useState(''); const handleChange = (event) => { setValue(event.target.value); }; return ( ); }; ``` TypeScript ругается на параметр `event` — пишет, что он имеет неявный тип `any`. Я пробовал писать `event: any`, но это плохая практика, и линтер тоже ругается. Пробовал искать в интернете, но везде разные варианты: кто-то использует `React.ChangeEvent`, кто-то `React.FormEvent`, а кто-то вообще `Event`. Подскажите, какой тип правильный и почему? И как правильно обрабатывать событие, если у меня, например, textarea или select? Заранее спасибо за помощь! <<>>

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

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

Основная причина путаницы в том, что в React существует несколько типов событий, и каждый из них предназначен для своей цели. Для обработки изменения значения в <input>, <textarea> или <select> правильным типом будет:

React.ChangeEvent<HTMLInputElement>

Почему именно ChangeEvent, а не FormEvent или любой другой? Потому что onChange в React — это синтетическое событие, которое специально создано для отслеживания изменений в элементах формы. Тип React.ChangeEvent является наиболее точным для этого случая.

Правильный код с типизацией

import React, { useState } from 'react';

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

  // ✅ Правильная типизация
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

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

Вариации для разных элементов формы

В зависимости от элемента, который вы используете, меняется только дженерик (generic parameter) внутри угловых скобок:

  • Для <input> — React.ChangeEvent<HTMLInputElement>
  • Для <textarea> — React.ChangeEvent<HTMLTextAreaElement>
  • Для <select> — React.ChangeEvent<HTMLSelectElement>

Пример с несколькими полями

import React, { useState } from 'react';

interface FormData {
  name: string;
  bio: string;
  country: string;
}

const MyForm: React.FC = () => {
  const [formData, setFormData] = useState<FormData>({
    name: '',
    bio: '',
    country: ''
  });

  // Универсальный обработчик
  const handleChange = (
    event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
  ) => {
    const { name, value } = event.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  return (
    <form>
      <input 
        name="name" 
        value={formData.name} 
        onChange={handleChange} 
      />
      <textarea 
        name="bio" 
        value={formData.bio} 
        onChange={handleChange} 
      />
      <select 
        name="country" 
        value={formData.country} 
        onChange={handleChange}
      >
        <option value="">Выберите</option>
        <option value="ru">Россия</option>
        <option value="us">США</option>
      </select>
    </form>
  );
};

Почему не стоит использовать event: any или Event

  • event: any — отключает всю типовую безопасность TypeScript. Вы не получите автодополнения для event.target.value и не увидите ошибки, если случайно обратитесь к несуществующему свойству.
  • Event — это базовый тип DOM-события. У него нет свойства target в том виде, в котором оно нужно (с value). React использует синтетические события, которые являются оберткой над нативными.
  • React.FormEvent — этот тип предназначен для события onSubmit у формы, а не для onChange у инпута.

Дополнительный совет: извлечение типа в отдельную переменную

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

type InputChangeEvent = React.ChangeEvent<HTMLInputElement>;

const handleChange = (event: InputChangeEvent) => {
  setValue(event.target.value);
};

Надеюсь, теперь стало понятнее! Главное правило: для onChange всегда используйте React.ChangeEvent с соответствующим HTML-элементом в дженерике.

<<>>

Альтернативный подход: использование типов из @types/react

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

Например, тип React.ChangeEventHandler<HTMLInputElement> — это готовый тип для функции-обработчика:

import React, { useState } from 'react';

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

  // Используем ChangeEventHandler вместо ChangeEvent
  const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
    setValue(event.target.value);
  };

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

Этот подход особенно удобен, когда вы передаете обработчик как пропс или используете его в хуках. Он делает код более декларативным и сокращает количество повторяющегося текста.

Также можно создать свой кастомный тип для часто используемых комбинаций:

type InputHandler = React.ChangeEventHandler<HTMLInputElement>;
type TextareaHandler = React.ChangeEventHandler<HTMLTextAreaElement>;

const handleInputChange: InputHandler = (e) => {
  console.log(e.target.value);
};

const handleTextareaChange: TextareaHandler = (e) => {
  console.log(e.target.value);
};

Оба подхода (с ChangeEvent и ChangeEventHandler) абсолютно корректны. Выбирайте тот, который вам удобнее в конкретной ситуации. Главное — не используйте any!