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

TypeScript中使用forwardRef与HOC的类型兼容问题排查

解决React高阶组件(HOC)结合MUI ButtonBase的TypeScript类型问题

问题背景

开发基于MUI的React高阶组件withTouchRipple,初始代码用any作为props类型可正常运行,但希望替换为泛型PROPS以暴露原组件的props接口,修改后触发TypeScript编译报错。

初始可运行代码

import React, { type FC, forwardRef } from 'react'
import { ButtonBase, ButtonBaseProps } from '@mui/material'

export default function withTouchRipple<PROPS>(OriginalComponent: FC<PROPS>) {
  return forwardRef((props: any, ref) => <ButtonBase {...props} ref={ref} component={OriginalComponent} />)
}

修改后报错的代码

import React, { type FC, forwardRef } from 'react'
import { ButtonBase, ButtonBaseProps } from '@mui/material'

export default function withTouchRipple<PROPS>(OriginalComponent: FC<PROPS>) {
  return forwardRef((props: PROPS, ref) => <ButtonBase {...props} ref={ref} component={OriginalComponent} />)
}

TypeScript编译错误

No overload matches this call.
Overload 1 of 3, '(props: { href: string; } & { action?: Ref | undefined; centerRipple?: boolean | undefined; children?: ReactNode; classes?: Partial | undefined; ... 10 more ...; touchRippleRef?: Ref<...> | undefined; } & Omit<...> & CommonProps & Omit<...>): Element', gave the following error.
Type 'ForwardedRef' is not assignable to type '((instance: HTMLAnchorElement | null) => void) | RefObject | null | undefined'.
Type 'MutableRefObject' is not assignable to type '((instance: HTMLAnchorElement | null) => void) | RefObject | null | undefined'.
Type 'MutableRefObject' is not assignable to type 'RefObject'.
Types of property 'current' are incompatible.
Type 'unknown' is not assignable to type 'HTMLAnchorElement | null'.
Overload 2 of 3, '(props: { component: FC; } & { action?: Ref | undefined; centerRipple?: boolean | undefined; children?: ReactNode; ... 11 more ...; touchRippleRef?: Ref<...> | undefined; } & Omit<...> & CommonProps & DistributiveOmit<...>): Element | null', gave the following error.
Type 'PROPS & { ref: ForwardedRef; component: FC; }' is not assignable to type 'IntrinsicAttributes & { component: FC; } & { action?: Ref | undefined; centerRipple?: boolean | undefined; ... 12 more ...; touchRippleRef?: Ref<...> | undefined; } & Omit<...> & CommonProps & DistributiveOmit<...>'.
Type 'PROPS & { ref: ForwardedRef; component: FC; }' is not assignable to type 'DistributiveOmit<PropsWithRef, keyof CommonProps | "tabIndex" | "children" | "action" | "centerRipple" | "disabled" | "disableRipple" | "disableTouchRipple" | ... 6 more ... | "touchRippleRef">'.
Overload 3 of 3, '(props: DefaultComponentProps<ExtendButtonBaseTypeMap<ButtonBaseTypeMap<{}, "button">>>): Element | null', gave the following error.
Type 'ForwardedRef' is not assignable to type '((instance: HTMLButtonElement | null) => void) | RefObject | null | undefined'.
Type 'MutableRefObject' is not assignable to type '((instance: HTMLButtonElement | null) => void) | RefObject | null | undefined'.
Type 'MutableRefObject' is not assignable to type 'RefObject'.
Types of property 'current' are incompatible.
Type 'unknown' is not assignable to type 'HTMLButtonElement | null'.ts(2769)
index.d.ts(805, 46): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & { href: string; } & { action?: Ref | undefined; centerRipple?: boolean | undefined; children?: ReactNode; ... 11 more ...; touchRippleRef?: Ref<...> | undefined; } & Omit<...> & CommonProps & Omit<...>'
index.d.ts(805, 46): The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & { action?: Ref | undefined; centerRipple?: boolean | undefined; children?: ReactNode; ... 11 more ...; touchRippleRef?: Ref<...> | undefined; } & Omit<...> & CommonProps & Omit<...>'

解决方案

报错核心原因:

  1. forwardRef未指定泛型类型,导致ref被推断为unknown,与ButtonBase期望的ref类型不匹配;
  2. 未正确处理ButtonBase props与原组件props的类型交集,且ref传递逻辑未明确类型约束。

修正后的代码方案1(基础兼容版)

import React, { type FC, forwardRef, type PropsWithRef } from 'react'
import { ButtonBase, ButtonBaseProps } from '@mui/material'

export default function withTouchRipple<PROPS, REF>(OriginalComponent: FC<PropsWithRef<PROPS>>) {
  return forwardRef<REF, PROPS>((props, ref) => (
    <ButtonBase {...props} component={OriginalComponent} ref={ref as React.Ref<unknown>} />
  ))
}

修正后的代码方案2(精确类型约束版)

import React, { type FC, forwardRef, type PropsWithRef } from 'react'
import { ButtonBase, type ButtonBaseProps } from '@mui/material'

// 排除ButtonBase自带的props,避免与原组件props冲突
type PropsWithoutButtonBase<PROPS> = Omit<PROPS, keyof ButtonBaseProps>

export default function withTouchRipple<PROPS, REF>(OriginalComponent: FC<PropsWithRef<PropsWithoutButtonBase<PROPS>>>) {
  return forwardRef<REF, PropsWithoutButtonBase<PROPS>>((props, ref) => (
    <ButtonBase component={OriginalComponent} ref={ref}>
      {props}
    </ButtonBase>
  ))
}

关键说明

  • 给forwardRef显式指定泛型参数<REF, PROPS>,明确ref的目标类型和props类型;
  • 使用PropsWithRef包裹原组件props,确保ref能正确传递;
  • 若原组件props与ButtonBase props存在重叠,用Omit排除重复属性,避免类型冲突;
  • 类型断言ref as React.Ref<unknown>仅在确保类型安全的场景下使用,用于兼容不同ref类型约束。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 17:17:56