Типы событий в React и TypeScript
- пятница, 29 декабря 2023 г. в 00:00:20
Эта статья — перевод оригинальной статьи "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.
К счастью, есть несколько решений:
Первое решение - навести курсор на свойство, которое вы пытаетесь передать:
<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} />;Иногда не нужно вводить всю функцию. Вы хотите ввести только событие.
Чтобы извлечь нужный тип события, нужно немного станцевать танец с бубном.
Сначала создайте встроенную функцию внутри onChange.
<input onChange={(e) => {}} />Теперь у вас есть доступ к e, вы можете навести на него курсор и получить нужный тип события:

Наконец, вы можете скопировать этот тип и использовать его для ввода своей функции onChange:
import React from "react";
const onChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
console.log(e);
};
<input onChange={onChange} />;Однако это все равно кажется медленным. Есть ли лучший способ?
Ускорить этот процесс можно, убрав этап проверки типа обработчика. Было бы здорово сказать: "Мне нужен такой-то тип обработчика", а TypeScript сам разберется со всем остальным.
Для этого мы можем использовать помощник типа под названием ComponentProps, о котором я уже писал ранее.
import React from "react";
const onChange: React.ComponentProps<"input">["onChange"] =
(e) => {
console.log(e);
};
<input onChange={onChange} />;Передавая input в ComponentProps, мы сообщаем TypeScript, что нам нужно свойство для элемента input.
Затем мы берем свойство onChange и используем его для нашей функции.
Спасибо Себастьяну Лорберу за этот совет!
Это всё хорошо, но мы все равно возвращаемся к необходимости вводить функцию 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 - отличная альтернатива.