Эта статья — перевод оригинальной статьи "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
- отличная альтернатива.