如何在React组件中指定forwardRef为可选?附TS报错解决
图标组件与按钮集成的TypeScript问题及实践探讨
我定义了一个导出多个图标的图标库,每个图标都会将ref转发给内部的svg,示例代码如下:
type ReactIconProps = SVGProps<SVGSVGElement>; function CheckIconInner(props: ReactIconProps, ref: Ref<SVGSVGElement>) { return ( <svg fill="none" height="1em" ref={ref} stroke="currentColor" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" {...props} > <path d="M20 6L9 17L4 12" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} /> </svg> ); } const CheckIcon = forwardRef(CheckIconInner);
我希望将这些图标作为按钮组件的props传入,让按钮可以包含图标,代码如下:
type ReactButtonProps = ButtonHTMLAttributes<HTMLButtonElement>; export interface ButtonProps extends ReactButtonProps { Icon?: ComponentType<ReactIconProps>; } function Button({ children, Icon, ...props }: ButtonProps) { return ( <button {...props}> {Icon !== undefined && <Icon />} {children} </button> ); } export default function App() { return <Button Icon={CheckIcon}>OK</Button>; }
但当将图标传入按钮(<Button Icon={CheckIcon}>OK</Button>)时,出现了TypeScript错误:
TS2322: Type 'ForwardRefExoticComponent<Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>>' is not assignable to type 'ComponentType<ReactIconProps> | undefined'. Type 'ForwardRefExoticComponent<Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>>' is not assignable to type 'FunctionComponent<ReactIconProps>'. Types of parameters 'props' and 'props' are incompatible. Type 'ReactIconProps' is not assignable to type 'Omit<ReactIconProps, "ref"> & RefAttributes<SVGSVGElement>'. Type 'SVGProps<SVGSVGElement>' is not assignable to type 'RefAttributes<SVGSVGElement>'. Types of property 'ref' are incompatible. Type 'LegacyRef<SVGSVGElement> | undefined' is not assignable to type 'Ref<SVGSVGElement> | undefined'. Type 'string' is not assignable to type 'Ref<SVGSVGElement> | undefined'.
问题核心在于图标组件需要处理ref,但当前的类型定义存在冲突。
1. 如何指定传入图标组件的ref为可选?
可以通过调整ButtonProps中Icon的类型定义,适配转发ref后的组件类型,有两种常用方案:
方案一:使用PropsWithoutRef简化类型
import { ComponentType, PropsWithoutRef } from 'react'; export interface ButtonProps extends ReactButtonProps { Icon?: ComponentType<PropsWithoutRef<ReactIconProps>>; }
PropsWithoutRef<ReactIconProps>会自动排除ref属性,让TypeScript允许不传递ref的情况下使用图标组件,完美兼容转发ref的组件类型。
方案二:明确指定ForwardRef类型
export interface ButtonProps extends ReactButtonProps { Icon?: React.ForwardRefExoticComponent< React.PropsWithoutRef<ReactIconProps> & React.RefAttributes<SVGSVGElement> >; }
这种方式更精准地匹配转发ref后的图标组件类型,适合需要明确类型细节的场景。
另外,也可以在图标组件内部将ref参数设为可选:
function CheckIconInner(props: ReactIconProps, ref?: Ref<SVGSVGElement>) { // 原有代码保持不变 } const CheckIcon = forwardRef(CheckIconInner);
不过更推荐调整按钮的Icon属性类型,这样能更好地兼容所有转发ref的图标组件。
2. 在图标组件中使用ref是否是良好实践?
在图标组件中使用ref是合理且具有实际价值的实践,存在不少适用场景:
- 无障碍访问优化:通过ref获取SVG元素,可设置焦点状态、添加屏幕阅读器相关属性,提升组件的无障碍性;
- 动画效果控制:需要直接操作SVG元素实现复杂动画时,ref能提供直接访问DOM节点的途径;
- 动态尺寸测量:在需要实时获取图标宽高信息时,ref可以帮助获取真实的DOM尺寸;
- 第三方工具集成:某些第三方库可能需要直接访问SVG元素,转发ref能满足这类集成需求。
虽然通常建议优先通过props和状态管理控制组件、避免过度依赖ref,但在图标这类封装原生SVG的组件中,转发ref属于合理的使用场景,主流组件库的广泛应用也印证了这一点。
内容的提问来源于stack exchange,提问作者Naresh
相关产品推荐
相关产品推荐

