给 React 事件添加类型的几种姿势
本文参考了 Matt Pocock 的文章 Event Types in React and TypeScript ,有条件的可以阅读英文原版。
一、前言
用 React 和 TypeScript 开发时,会经常碰到这个报错:

image-20231221092602795
当我们给不同的 DOM 元素添加事件处理函数时,onChange 中参数 e 接收到的事件类型是不同的,我们必须为e 指定正确的类型。
二、解决方案
1. 鼠标悬浮
我们把鼠标悬浮在 onChange 上,IDE 会给出相应的类型,直接复制,再将类型添加给声明的事件处理函数:

image-20231221093203173
我们需要的是
?:后面的这部分代码。

image-20231221093241375
2. 内联函数
当我们只想为 e 添加类型时,首先要知道 e 的类型是什么。
创建一个内联事件处理函数,将鼠标悬浮在参数 e 上,IDE 会给出正确的类型,选中并复制:

image-20231221094156684
然后添加到我们的处理函数中:

image-20231221094300770
3. 使用 React.ComponentProps
React.ComponentProps 是一个类型工具,用于获取组件或元素的类型。

image-20231221095115296
4. EventFor 工具类型
第三种方案非常方便,但是如果我们只是想给 e 添加类型,有没有更优雅的写法呢?
我们可以用 TypeScript 内置的工具类型 Parameters 、NonNullable 以及索引访问来实现:

image-20231221095612679
解释一下上面的代码:
React.ComponentProps<'input'>['onChange']在第三种方案中用过,但是这个类型包含undefined,需要使用NonNullable把它去掉;NonNullable用于去除某个类型中的null和undefined;Parameters用于获取某个函数的所有参数类型,返回一个类型数组,再使用[0]取出第一个参数的类型。
如此一来,我们就拿到了参数 e 的正确类型。
但是,上面这种写法有点冗杂,我们可以再进一步,封装一个工具类型:
type GetEventHandlers<
T extends keyof JSX.IntrinsicElements,
> = Extract<keyof JSX.IntrinsicElements[T], `on${string}`>
type EventFor<
TElement extends keyof JSX.IntrinsicElements,
THandler extends GetEventHandlers<TElement>,
> = JSX.IntrinsicElements[TElement][THandler] extends
| (((e: infer TEvent) => any) | undefined)
? TEvent
: never上面这段代码需要一些类型体操的知识,这里只解释大概原理:
JSX.IntrinsicElements是一个interface,其中声明了所有 HTML 元素的类型;GetEventHandlers接收一个元素类型,通过Extract提取该元素所有onXxxx形式的属性,也就是所有的事件:
image-20231221101337607EventFor接收两个泛型,分别代表元素类型和事件名称,根据这两个泛型从JSX.IntrinsicElements中取出对应事件的处理函数的类型声明,然后通过extends以及infer关键字推断参数e的类型并返回。
效果:

image-20231221102009771