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

TypeScript实现带forwardRef的多态Button组件ref类型匹配问题

React+TypeScript多态Button组件ref类型联动实现方案

需求说明

开发支持多态渲染的Button组件,可通过as属性指定最终渲染的原生标签类型:

  • as="button"时渲染为原生<button>标签,支持button所有原生属性,ref类型匹配HTMLButtonElement
  • as="a"时渲染为原生<a>标签,支持a标签所有原生属性,ref类型匹配HTMLAnchorElement
  • 组件支持outlined、className、children等自定义公共属性

预期使用效果

// 渲染按钮
<Button as="button" type="submit">提交按钮</Button>
// 渲染链接
<Button as="a" href="/home" target="_blank">首页链接</Button>

// ref类型自动匹配,类型错误会被TS识别
const btnRef = useRef<HTMLButtonElement>(null)
const linkRef = useRef<HTMLAnchorElement>(null)
<Button as="button" ref={btnRef}>按钮</Button> // 类型校验通过
<Button as="a" ref={linkRef}>链接</Button> // 类型校验通过
// <Button as="button" ref={linkRef}> 会报类型不匹配错误

问题原因

原有实现存在三个核心问题:

  1. ref直接定义为HTMLAnchorElement | HTMLButtonElement联合类型,无法和as属性做联动推导
  2. 原生属性类型使用React.HTMLAttributes不准确,会丢失button、a标签的专属属性类型提示
  3. React自带的forwardRef类型默认不支持泛型多态推导,无法根据props动态推导ref类型

完整实现代码

import { forwardRef, Ref, ReactElement } from "react";

// 组件公共基础属性
type ButtonBaseProps = {
  outlined?: boolean;
  children?: React.ReactNode;
  className?: string;
};

// 多态标签映射表:统一维护as值对应的元素类型、原生属性类型
type AsTagMap = {
  a: {
    element: HTMLAnchorElement;
    nativeProps: React.AnchorHTMLAttributes<HTMLAnchorElement>;
  };
  button: {
    element: HTMLButtonElement;
    nativeProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
  };
};

// 泛型组件属性:根据传入的as值自动推导对应属性、ref类型
type PolymorphicButtonProps<As extends keyof AsTagMap = keyof AsTagMap> = {
  as?: As;
} & ButtonBaseProps & AsTagMap[As]["nativeProps"];

// 用类型断言扩展forwardRef的泛型推导能力,实现类型联动
const Button = forwardRef<HTMLElement, PolymorphicButtonProps<keyof AsTagMap>>(
  function ForwardedButton(props, ref) {
    const { outlined, children, className, as: Component = "a", ...restProps } = props;
    
    const mergedClassName = [
      className,
      outlined ? "border-2 border-black" : ""
    ].filter(Boolean).join(" ");

    return (
      <Component ref={ref} className={mergedClassName} {...restProps}>
        {children}
      </Component>
    );
  }
) as <As extends keyof AsTagMap = "a">(
  props: PolymorphicButtonProps<As> & { ref?: Ref<AsTagMap[As]["element"]> }
) => ReactElement | null;

export default Button;

实现说明

  • 通过AsTagMap映射表统一维护多态标签的对应类型,后续扩展支持其他标签(比如div、span)时只需要在映射表加配置即可
  • 利用TypeScript泛型推导能力,根据传入的as值自动匹配对应的原生属性类型、ref类型,不需要手动写联合类型判断
  • 用类型断言修正forwardRef默认类型的推导缺陷,既保留了ref转发的能力,又实现了类型联动
  • 修正了原生属性类型定义,使用对应标签专属的HTMLAttributes类型,保留所有原生属性的类型提示

内容的提问来源于stack exchange,提问作者Tobi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:03:21