TypeScript多态React组件事件处理:currentTarget类型问题
React多态组件中动态适配HTML标签的事件类型问题
我实现了一个支持任意合法HTML标签的强类型TypeScript多态React组件,核心代码如下:
// polymorphic component with forwarded ref import React, { forwardRef, useCallback, type ComponentPropsWithRef, type ElementType, type JSX, type JSXElementConstructor, type Ref, } from 'react'; type IntrinsicAttributes< E extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>, > = JSX.LibraryManagedAttributes<E, ComponentPropsWithRef<E>>; export interface BoxOwnProps<E extends ElementType = ElementType> { as?: E; } export type BoxProps<E extends ElementType> = BoxOwnProps<E> & Omit<IntrinsicAttributes<E>, keyof BoxOwnProps>; export const Box = forwardRef(function Box( { as: Element = 'div', ...restProps }: BoxOwnProps, ref: Ref<Element> ) { const handleOnClick = useCallback((ev: HTMLElement) => { // 🚨 类型错误出现在这一行 console.log(ev.currentTarget); }, []); return <Element ref={ref} onClick={handleOnClick} {...restProps} />; }) as <E extends ElementType = 'div'>(props: BoxProps<E>) => JSX.Element;
我给handleOnClick的事件参数指定了HTMLElement类型(作为所有HTML DOM元素的父类型保证通用性),但访问event.currentTarget时出现类型错误。如果改成HTMLButtonElement这类特定类型能解决问题,但会破坏组件多态性——开发者用a标签时,事件类型又要改成HTMLAnchorElement。
请问是否存在可接收标签字符串的泛型HTMLElement类型?比如HTMLElement<'button'>,能根据HTML标签名动态设置事件类型?
解决方案
有的,你可以借助TypeScript内置的JSX.IntrinsicElements映射类型,结合泛型推导对应标签的元素类型,调整后的代码如下:
import React, { forwardRef, useCallback, type ComponentPropsWithRef, type ElementType, type JSX, type JSXElementConstructor, type Ref, } from 'react'; type IntrinsicAttributes< E extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>, > = JSX.LibraryManagedAttributes<E, ComponentPropsWithRef<E>>; // 工具类型:根据标签名推导对应DOM元素类型 type ElementFromTag<E extends ElementType> = E extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[E] extends { ref?: Ref<infer T> } ? T : HTMLElement : HTMLElement; export interface BoxOwnProps<E extends ElementType = ElementType> { as?: E; } export type BoxProps<E extends ElementType> = BoxOwnProps<E> & Omit<IntrinsicAttributes<E>, keyof BoxOwnProps>; export const Box = forwardRef(function Box<E extends ElementType = 'div'>( { as: Element = 'div', ...restProps }: BoxProps<E>, ref: Ref<ElementFromTag<E>> ) { const handleOnClick = useCallback((ev: React.MouseEvent<ElementFromTag<E>>) => { console.log(ev.currentTarget); // 类型匹配,无错误 }, []); return <Element ref={ref} onClick={handleOnClick} {...restProps} />; }) as <E extends ElementType = 'div'>(props: BoxProps<E>) => JSX.Element;
关键说明
ElementFromTag工具类型:通过JSX.IntrinsicElements[E]获取对应标签的Props,提取其中ref关联的元素类型,实现标签到DOM元素类型的动态映射- 事件类型使用
React.MouseEvent<ElementFromTag<E>>,currentTarget会自动匹配当前使用的标签元素类型(比如as="a"时对应HTMLAnchorElement) - 组件函数和Ref都通过泛型
E关联,保证整个类型链的一致性
这样调整后,既保留了组件的多态性,又完美解决了currentTarget的类型错误问题。
内容的提问来源于stack exchange,提问作者mrbinky3000
相关产品推荐
相关产品推荐

