Avatar ·

Типизация событий React в TypeScript: как правильно указывать типы для обработчиков?

Всем привет! Я только начал осваивать TypeScript в связке с React и столкнулся с проблемой — не могу правильно понять, как типизировать события. В документации React на TypeScript написано много разных типов, но я запутался, какой именно использовать для каждой ситуации. Вот простой пример: у меня есть форма с инпутом, и я хочу обработать событие onChange. В JavaScript я писал просто: ```tsx function handleChange(e) { setValue(e.target.value); } ``` Но в TypeScript это выдаёт ошибку: "Parameter 'e' implicitly has an 'any' type". Я попробовал написать `React.ChangeEvent`, но не уверен, что это правильно. А что делать с onSubmit формы? Или с onClick кнопки? Есть ли универсальный тип для всех событий? Ещё вопрос: как типизировать кастомные события или события, которые приходят из дочерних компонентов? Например, я передаю в компонент пропс `onInputChange`, который должен принимать событие от `` и что-то с ним делать. Какой тип указать для этого пропса? Буду благодарен за подробное объяснение с примерами. Хочется понять не просто "скопируй этот тип", а как вообще разобраться, какой тип использовать в каждой конкретной ситуации. Спасибо! <<>>

Основные типы событий React в TypeScript

В React все события обёрнуты в синтетические обёртки SyntheticEvent, которые имеют свои типовые дженерики. Главное правило: тип события зависит от элемента, к которому оно привязано, и от типа самого события. Универсального типа "на все случаи" нет, но все они наследуются от React.SyntheticEvent.

1. Самые частые события

Вот таблица соответствий для популярных элементов и событий:

  • onChange для <input>, <select>, <textarea> → React.ChangeEvent<HTMLInputElement> (или соответствующий элемент)
  • onSubmit для <form> → React.FormEvent<HTMLFormElement>
  • onClick для <button>, <div> → React.MouseEvent<HTMLButtonElement>
  • onKeyDown → React.KeyboardEvent<HTMLInputElement>
  • onFocus / onBlur → React.FocusEvent<HTMLInputElement>

2. Пример с формой

import React, { useState } from 'react';

function MyForm() {
  const [value, setValue] = useState('');

  // Тип события: ChangeEvent для HTMLInputElement
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  // Тип события: FormEvent для HTMLFormElement
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    console.log('Submitted:', value);
  };

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

3. Типизация пропсов-обработчиков

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

interface InputProps {
  onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  value: string;
}

function CustomInput({ onInputChange, value }: InputProps) {
  return <input value={value} onChange={onInputChange} />;
}

// Использование:
<CustomInput 
  value={text} 
  onInputChange={(e) => setText(e.target.value)} 
/>

4. Как не ошибиться с выбором типа

Есть простой лайфхак: наведите курсор на событие в JSX — IDE покажет ожидаемый тип. Например, если написать onChange={(e) => ...} и навести на e, TypeScript подскажет точный тип. Также можно использовать обобщённый тип React.ChangeEvent<HTMLElement>, но это менее безопасно — вы потеряете автодополнение для свойств вроде e.target.value (оно есть только у HTMLInputElement).

5. Для кастомных событий

Если вы создаёте свои события (например, через EventEmitter или CustomEvent), используйте обычные типы TypeScript:

interface MyCustomEvent {
  detail: { userId: number };
}

function handleCustom(e: CustomEvent<MyCustomEvent>) {
  console.log(e.detail.userId);
}

В React для работы с DOM-событиями всегда используйте типы из пространства React. Запомните шаблон: React.[ТипСобытия]Event<[HTMLЭлемент]>. Это покрывает 90% случаев.

<<>>

Упрощённый подход: использование обобщённых типов

Если вы не хотите каждый раз вспоминать конкретный HTML-элемент, можно использовать более общие типы. Например, React.ChangeEvent<HTMLElement> или React.MouseEvent<HTMLElement>. Однако помните, что HTMLElement — это самый базовый интерфейс, и у него нет специфических свойств вроде value или checked. Для доступа к ним придётся делать приведение типа:

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

Это работает, но менее безопасно — вы теряете проверку типов на этапе компиляции. Лучше использовать ChangeEvent<HTMLInputElement> напрямую.

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

type InputHandler = (e: React.ChangeEvent<HTMLInputElement>) => void;

const handleChange: InputHandler = (e) => {
  setValue(e.target.value);
};

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