打字稿:React.forwardRef 带有来自@material-ui 的通用道具

Posted

技术标签:

【中文标题】打字稿:React.forwardRef 带有来自@material-ui 的通用道具【英文标题】:Typescript: React.forwardRef with generic props from @material-ui 【发布时间】:2020-10-19 21:28:27 【问题描述】:

我正在尝试在@material-ui Button 组件之上添加自定义逻辑。为此,我正在创建一个包装原始 Button 并正常运行的组件。

我的问题是正确输入,尤其是从原始组件中获取道具。我一直关注https://material-ui.com/guides/typescript/#usage-of-component-prop,直到我添加了React.forwardRef()

没有forwardRef的工作版:

import  Button, ButtonProps  from '@material-ui/core'
import React from 'react'

type ExtraProps = 
  target?: string
  rel?: string
  href?: string
  color?: 'default' | 'primary' | 'secondary'


// see https://material-ui.com/guides/typescript/#usage-of-component-prop
type Props<C extends React.ElementType> = ButtonProps<C,  component?: C > & ExtraProps

const CustomButton = <C extends React.ElementType>(
  children,
  target,
  rel,
  href,
  ...rest
: Props<C>) => 
  const relValue = target === '_blank' ? 'noopener noreferrer' : rel

  const linkValues = href ?  href, target, rel: relValue  : undefined

  return (
    <Button ...linkValues ...rest>
      children
    </Button>
  )

带有React.forwardRef的非工作版本:

import  Button, ButtonProps  from '@material-ui/core'
import React from 'react'

type ExtraProps = 
  target?: string
  rel?: string
  href?: string
  color?: 'default' | 'primary' | 'secondary'


// see https://material-ui.com/guides/typescript/#usage-of-component-prop
export type Props<C extends React.ElementType> = ButtonProps<C,  component?: C > & ExtraProps

const CustomButton = React.forwardRef<htmlButtonElement, Props<React.ElementType>>(
  ( children, target, rel, href, ...rest , ref) => 
    const relValue = target === '_blank' ? 'noopener noreferrer' : rel

    const linkValues = href ?  href, target, rel: relValue  : undefined

    return (
      <Button ...linkValues ...rest ref=ref>
        children
      </Button>
    )
  
)

当我说“不工作”时,是因为CustomButton 的类型是:

React.ForwardRefExoticComponent<Pick<Props<React.ElementType<any>>, string | number | symbol> & React.RefAttributes<HTMLButtonElement>>

而不是

<C extends React.ElementType<any>>( children, target, rel, href, ...rest : Props<C>) => JSX.Element

这意味着我可以将任何道具传递给我的CustomButton,而 TS 根本不会执行合同。

我应该如何修复带有React.forwardRef() 的版本才能正确输入?

Codesandbox 演示:https://codesandbox.io/s/boring-hertz-0hutt?file=/src/App.tsx

【问题讨论】:

【参考方案1】:

forwardRef 不是通用的。这是万恶之源。我也在努力实现类型安全。现在我有这个解决方案:

type As = 'button' | 'a' | 'span';

type Element<T extends As = 'button'> = T extends 'span'
    ? HTMLSpanElement
    : T extends 'a'
    ? HTMLAnchorElement
    : HTMLButtonElement;

interface CommonButtonProps<T extends As> 
    view?: View;
    size?: Size;
    disabled?: boolean;
    iconLeft?: ReactElement;
    iconRight?: ReactElement;
    className?: string;
    progress?: boolean;
    baseElement?: T;


export type ButtonProps<T extends As = 'button'> = CommonButtonProps<T> &
    (T extends 'a'
        ? JSX.IntrinsicElements['a']
        : T extends 'span'
        ? JSX.IntrinsicElements['span']
        : T extends undefined
        ? JSX.IntrinsicElements['button']
        : JSX.IntrinsicElements['button']);

const Button = (
     children, baseElement, ...props : PropsWithChildren<ButtonProps<As>>,
    ref: React.Ref<Element<As>>,
) => 
    return (
        <StyledButton ...props as=baseElement ref=ref>
            children
        </StyledButton>
    );
;

function genericForwardRef<T extends ForwardRefRenderFunction<any, any>>(Component: T) 
    const ForwardedComponent = forwardRef(Component);

    return <U extends As = 'button'>(
        props: PropsWithChildren<PropsWithoutRef<ButtonProps<U>> & RefAttributes<Element<U>>>,
    ) => <ForwardedComponent ...props />;


export default genericForwardRef(Button);

(注:我正在使用样式组件)

【讨论】:

以上是关于打字稿:React.forwardRef 带有来自@material-ui 的通用道具的主要内容,如果未能解决你的问题,请参考以下文章

带有打字稿的猫鼬,来自猫鼬的错误“连接”

带有箭头功能的打字稿装饰器

如何防止 VS 2022 在 esproj 中显示带有 d.ts 文件的打字稿错误

带有 React.forwardRef 功能组件的 VSCode TS 错误

带有打字稿的角度材料设计

带有打字稿的枚举