You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 19:15:09