React 中使用 ComponentPropsWithoutRef 扩展 html 元素

本文翻译自 React with TypeScript Cheatsheet

有时会需要基于原生 HTML 元素扩展一些自定义 React 组件,这个过程中你需要手工指定需要用到的原生 HTML 元素本机属性,如 onClick type 等,ComponentPropsWithoutRef 类型可以帮你获取 HTML 元素的所有本机属性作为组件的 props 类型,从而避免手工劳动。

例如,原生 button 元素已经有 onClick 属性,但是当你创建一个 React 组件时,你通常需要使用或像这样为 <Button>定义:

type ButtonProps = {
  children: React.ReactNode
  onClick: () => void
}

const Button = ({ children, onClick }: ButtonProps) => {
  return <button onClick={onClick}>{children}</button>
}

然后,感觉需求继续为 ButtonProps 添加另一个属性:

type ButtonProps = {
  children: React.ReactNode
  onClick: () => void
  disabled: boolean
  type: 'button' | 'submit' | 'reset' | undefined 
}

这样一个一个添加就很麻烦,使用 ComponentPropsWithoutRef 类型可以在扩展自定义组件式不需要手工添加这些本机 HTML 属性, 简单地创建一个具有所有本机 button 属性类型的 Button 类型:

type ButtonProps = React.ComponentPropsWithoutRef<"button">
const Button = ({ children, onClick, type }: ButtonProps) => {
  return (
    <button onClick={onClick} type={type}>
      {children}
    </button>
  )
}

ComponentPropsWithoutRef<"button"> 类型具有原生 HTML button 元素的所有属性。

如果您想创建一个 <Img> 组件,那么您可以使用 ComponentPropsWithoutRef<"img"> 类型:

type ImgProps = React.ComponentPropsWithoutRef<"img">
const Img = ({ src, loading }: ImgProps) => {
  return <img src={src} loading={loading} />
}

只需要改变 ComponentPropsWithoutRef<T> 的泛型类型就可以扩展不同的 HTML 元素。例如:

  • ComponentPropsWithoutRef<'img'> 扩展 <img> 元素
  • ComponentPropsWithoutRef<'button'> 扩展 <button> 元素
  • ComponentPropsWithoutRef<'a'> 扩展 <a> 元素

当你需要添加原生 HTML 元素中不存在的自定义 prop 时,你可以创建一个 interface 扩展原生属性的 prop,如下所示:

interface ImgProps extends React.ComponentPropsWithoutRef<"img"> {
  customProp: string;
}
const Img = ({ src, loading, customProp }: ImgProps) => {
  // use the customProp here..
  return <img src={src} loading={loading} />;
}

如果您需要自定义属性来确定组件的外观,这将特别有用。

在下面的例子中,自定义属性 variant 用于确定 <h1> 元素的 style: color CSS 属性:

interface headerProps extends React.ComponentPropsWithoutRef<"h1"> {
  variant: "primary" | "secondary";
}

const Header = ({ children, variant }: headerProps) => {
  return (
    <h1 style={{color: variant === "primary" ? "black" : "red" }}>
      {children}
    </h1>
  );
};

ComponentPropsWithoutRef 类型可以轻松创建一个作为原生 HTML 元素扩展的组件,而无需自己输入所有可能的 prop 参数。 您甚至可以通过扩展 interface 来添加额外的属性。

当您需要将引用转发给组件的子项时,可以使用 ComponentPropsWithoutRef 接口的孪生兄弟 ComponentPropsWithRef

在这里了解更多关于 ref 转发的信息:https://reactjs.org/docs/forwarding-refs.html

ComponentPropsWithoutRef[Element]HTMLAttributes

如果你之前曾在 React 中使用过 TypeScript,那么你可能熟悉 [Element]HTMLAttributes,你可以使用它来扩展 HTML 元素,如下所示:

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
type ImgProps = React.ImgHTMLAttributes<HTMLImageElement>

这些 [Element]HTMLAttributes 接口产生与 ComponentPropsWithoutRef 接口相同的类型,但它们更为冗长,因为您需要对每个 HTML 元素使用不同的接口和泛型。

另一方面,ComponentPropsWithoutRef 只需要你改变泛型类型 <T>。当然,两者都适合在 React 组件中扩展 HTML 元素。