TypeScript实现带forwardRef的多态Button组件ref类型匹配问题
需求说明
开发支持多态渲染的Button组件,可通过as属性指定最终渲染的原生标签类型:
as="button"时渲染为原生<button>标签,支持button所有原生属性,ref类型匹配HTMLButtonElementas="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}> 会报类型不匹配错误
问题原因
原有实现存在三个核心问题:
- ref直接定义为
HTMLAnchorElement | HTMLButtonElement联合类型,无法和as属性做联动推导 - 原生属性类型使用
React.HTMLAttributes不准确,会丢失button、a标签的专属属性类型提示 - 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
相关产品推荐
相关产品推荐

