[Перевод] Типы событий в React и TypeScript
Эта статья — перевод оригинальной статьи «Event Types in React and TypeScript».
Также я веду телеграм канал «Frontend по-флотски», где рассказываю про интересные вещи из мира разработки интерфейсов.
Проблема
При работе с React и TypeScript вы часто сталкиваетесь с подобными ошибками:
const onChange = (e) => {}; // Parameter 'e' implicitly has an 'any' type.
;
Не всегда понятно, какой тип следует присвоить пременнойe
внутри функции onChange
.
Это может произойти с onClick
, onSubmit
или любым другим обработчиком событий, которые получают элементы DOM.
К счастью, есть несколько решений:
Решение 1: Наведите курсор, затем введите обработчик
Первое решение — навести курсор на свойство, которое вы пытаетесь передать:
;
Как вы можете видеть, этот тип получается удивительно длинным:
React.InputHTMLAttributes.onChange?:
React.ChangeEventHandler | undefined
Нужная нам часть выглядит следующим образом: React.ChangeEventHandler
.
Мы можем использовать его для нашей функции onChange:
import React from "react";
const onChange: React.ChangeEventHandler<
HTMLInputElement
> = (e) => {
console.log(e);
};
;
Решение 2: Вставьте функцию, а затем введите событие
Иногда не нужно вводить всю функцию. Вы хотите ввести только событие.
Чтобы извлечь нужный тип события, нужно немного станцевать танец с бубном.
Сначала создайте встроенную функцию внутри onChange
.
{}} />
Теперь у вас есть доступ к e
, вы можете навести на него курсор и получить нужный тип события:
Наконец, вы можете скопировать этот тип и использовать его для ввода своей функции onChange
:
import React from "react";
const onChange = (
e: React.ChangeEvent
) => {
console.log(e);
};
;
Однако это все равно кажется медленным. Есть ли лучший способ?
Решение 3: Используйте React.ComponentProps
Ускорить этот процесс можно, убрав этап проверки типа обработчика. Было бы здорово сказать: «Мне нужен такой-то тип обработчика», а TypeScript сам разберется со всем остальным.
Для этого мы можем использовать помощник типа под названием ComponentProps
, о котором я уже писал ранее.
import React from "react";
const onChange: React.ComponentProps<"input">["onChange"] =
(e) => {
console.log(e);
};
;
Передавая input
в ComponentProps
, мы сообщаем TypeScript, что нам нужно свойство для элемента input
.
Затем мы берем свойство onChange
и используем его для нашей функции.
Спасибо Себастьяну Лорберу за этот совет!
Решение 4: Используйте помощник EventFrom
Это всё хорошо, но мы все равно возвращаемся к необходимости вводить функцию onChange
.
Что, если мы хотим извлечь только тип события?
Для этого мы можем использовать комбинацию Parameters
, NonNullable
и индексированных типов доступа:
import React from "react";
const onChange = (
e: Parameters<
NonNullable["onChange"]>
>[0]
) => {};
Но это слишком большой объем кода.
Вместо этого давайте представим себе помощника типа под названием EventFor
:
Она принимает тип элемента и тип обработчика, а возвращает тип события. Вы получаете автозаполнение по каждому из параметров, и вам не нужно вводить функцию.
Проблема в том, что вам нужно держать относительно большой помощник типа в своей кодовой базе. Вот код:
type GetEventHandlers<
T extends keyof JSX.IntrinsicElements
> = Extract;
/**
* 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
> = JSX.IntrinsicElements[TElement][THandler] extends
| ((e: infer TEvent) => any)
| undefined
? TEvent
: never;
Какое решение следует использовать?
Лично я предпочитаю решение EventFor
. Возможно, это потому, что я его придумал, но вот почему оно мне нравится:
Это единое место, где вы можете получить тип события для любого элемента и обработчика.
Вы получаете автозаполнение типов элементов и обработчиков
Я предпочитаю вводить событие, а не функцию — просто мышечная память.
Но если вы не хотите держать рядом помощника типа, решение ComponentProps
— отличная альтернатива.