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


  1. sanex3339
    28.12.2023 15:23
    +2

    Мы делаем вот так

    import { ChangeEvent, MouseEvent } from 'react'
    
    const handleChange = (event: ChangeEvent<HTMLInputElement>) => {...}
    const handleClick = (event: MouseEvent<HTMLDivElement>) => {...}


    1. qmzik Автор
      28.12.2023 15:23

      Выходит используете Решение №2? :)


      1. sanex3339
        28.12.2023 15:23

        Да. Все остальные варианты, особенно 3 и 4 - оверхед как мне кажется


  1. mamont80
    28.12.2023 15:23

    Проблема поднята нужная. А ведь IDE уже знает нужный тип когда мы стоим тут: <input onChange={тут} />! Ведь есть же наверное горячие клавиши чтобы сформировать шаблон функции с уже заданным типом, без всех этих танцев с бубном? Если кто подскажет такое для WebStorm буду благодарен.