Эта статья — перевод оригинальной статьи "Event Types in React and TypeScript".
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Проблема
При работе с React и TypeScript вы часто сталкиваетесь с подобными ошибками:
const onChange = (e) => {}; // Parameter 'e' implicitly has an 'any' type.
 
<input onChange={onChange} />;Не всегда понятно, какой тип следует присвоить пременнойe внутри функции onChange.
Это может произойти с onClick, onSubmit или любым другим обработчиком событий, которые получают элементы DOM.
К счастью, есть несколько решений:
Решение 1: Наведите курсор, затем введите обработчик
Первое решение - навести курсор на свойство, которое вы пытаетесь передать:
<input onChange={onChange} />;Как вы можете видеть, этот тип получается удивительно длинным:
React.InputHTMLAttributes<HTMLInputElement>.onChange?:
  React.ChangeEventHandler<HTMLInputElement> | undefinedНужная нам часть выглядит следующим образом: React.ChangeEventHandler.
Мы можем использовать его для нашей функции onChange:
import React from "react";
 
const onChange: React.ChangeEventHandler<
  HTMLInputElement
> = (e) => {
  console.log(e);
};
 
<input onChange={onChange} />;Решение 2: Вставьте функцию, а затем введите событие
Иногда не нужно вводить всю функцию. Вы хотите ввести только событие.
Чтобы извлечь нужный тип события, нужно немного станцевать танец с бубном.
Сначала создайте встроенную функцию внутри onChange.
<input onChange={(e) => {}} />Теперь у вас есть доступ к e, вы можете навести на него курсор и получить нужный тип события:

Наконец, вы можете скопировать этот тип и использовать его для ввода своей функции onChange:
import React from "react";
 
const onChange = (
  e: React.ChangeEvent<HTMLInputElement>
) => {
  console.log(e);
};
 
<input onChange={onChange} />;Однако это все равно кажется медленным. Есть ли лучший способ?
Решение 3: Используйте React.ComponentProps
Ускорить этот процесс можно, убрав этап проверки типа обработчика. Было бы здорово сказать: "Мне нужен такой-то тип обработчика", а TypeScript сам разберется со всем остальным.
Для этого мы можем использовать помощник типа под названием ComponentProps, о котором я уже писал ранее.
import React from "react";
 
const onChange: React.ComponentProps<"input">["onChange"] =
  (e) => {
    console.log(e);
  };
 
<input onChange={onChange} />;Передавая input в ComponentProps, мы сообщаем TypeScript, что нам нужно свойство для элемента input.
Затем мы берем свойство onChange и используем его для нашей функции.
Спасибо Себастьяну Лорберу за этот совет!
Решение 4: Используйте помощник EventFrom
Это всё хорошо, но мы все равно возвращаемся к необходимости вводить функцию onChange.
Что, если мы хотим извлечь только тип события?
Для этого мы можем использовать комбинацию Parameters, NonNullable и индексированных типов доступа:
import React from "react";
 
const onChange = (
  e: Parameters<
    NonNullable<React.ComponentProps<"input">["onChange"]>
  >[0]
) => {};Но это слишком большой объем кода.
Вместо этого давайте представим себе помощника типа под названием EventFor:
Она принимает тип элемента и тип обработчика, а возвращает тип события. Вы получаете автозаполнение по каждому из параметров, и вам не нужно вводить функцию.
Проблема в том, что вам нужно держать относительно большой помощник типа в своей кодовой базе. Вот код:
type GetEventHandlers<
  T extends keyof JSX.IntrinsicElements
> = Extract<keyof JSX.IntrinsicElements[T], `on${string}`>;
 
/**
 * Provides the event type for a given element and handler.
 *
 * @example
 *
 * type MyEvent = EventFor<"input", "onChange">;
 */
export type EventFor<
  TElement extends keyof JSX.IntrinsicElements,
  THandler extends GetEventHandlers<TElement>
> = JSX.IntrinsicElements[TElement][THandler] extends
  | ((e: infer TEvent) => any)
  | undefined
  ? TEvent
  : never;Какое решение следует использовать?
Лично я предпочитаю решение EventFor. Возможно, это потому, что я его придумал, но вот почему оно мне нравится:
- Это единое место, где вы можете получить тип события для любого элемента и обработчика. 
- Вы получаете автозаполнение типов элементов и обработчиков 
- Я предпочитаю вводить событие, а не функцию - просто мышечная память. 
Но если вы не хотите держать рядом помощника типа, решение ComponentProps - отличная альтернатива.
Комментарии (4)
 - mamont8028.12.2023 15:23- Проблема поднята нужная. А ведь IDE уже знает нужный тип когда мы стоим тут: <input onChange={тут} />! Ведь есть же наверное горячие клавиши чтобы сформировать шаблон функции с уже заданным типом, без всех этих танцев с бубном? Если кто подскажет такое для WebStorm буду благодарен. 
 
           
 
sanex3339
Мы делаем вот так
qmzik Автор
Выходит используете Решение №2? :)
sanex3339
Да. Все остальные варианты, особенно 3 и 4 - оверхед как мне кажется