在 Typescript React App 中指定特定道具并接受一般 HTML 道具

Specify specific props and accept general HTML props in Typescript React App

我有一个 React Wrapper 组件,它接受一些道具,但将所有其他道具转发给子组件(尤其是与 className、id 等原生道具相关的内容)。

然而,当我传递本机道具时,Typescript 会抱怨。查看错误消息:

TS2339: Property 'className' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< { children?: ReactNode; }> & Readonly< WrapperProps>'.

如何获得具有特定 props 的组件也接受原生 props(而不 接受任何 props 并放弃类型检查)?

我的代码如下所示:

interface WrapperProps extends JSX.IntrinsicAttributes {
  callback?: Function
}

export class Wrapper extends React.Component<WrapperProps>{
  render() {
    const { callback, children, ...rest } = this.props;
    return <div {...rest}>
      {children}
    </div>;
  }
}

export const Test = () => {
  return <Wrapper className="test">Hi there</Wrapper>
}

仅供参考:我在这里发现了一个类似的问题,但答案基本上放弃了类型检查,我想避免这种情况:

我们可以看看 div props 是如何定义的:

interface IntrinsicElements {
    div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}

如果我们使用 React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> 作为基本类型,我们将拥有 div 的所有属性。由于 DetailedHTMLProps 只是将 ref 添加到 React.HTMLAttributes<HTMLDivElement>,我们可以将其用作获取所有 div 属性的基本接口:

interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
  callback?: Function
}

export class Wrapper extends React.Component<WrapperProps>{
  render() {
    const { callback, children, ...rest } = this.props;
    return <div {...rest}>
      {children}
    </div>;
  }
}

export const Test = () => {
  return <Wrapper className="test">Hi there</Wrapper> // works now
}

我的一个同事想通了。在这里分享以获得更广泛的知名度:

interface ComponentPropTypes = {
  elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
  ...
}


// Function component
function Component({
  elementName: Component = 'div',
  ...rest,
  // React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
  return <Component {...rest} />;
}

// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
  render() {
    const {
      elementName: Component,
      ...rest,
    } = this.props;
    return <Component {...rest} />
  }
}

JSX.IntrinsicElements 有这个信息,例如

const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
  <button {...props} className={`foo ${props.className}`} />
)

// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
  JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />

react-typescript-cheatsheet 项目中发现了这个。

查看 ComponentPropsComponentPropsWithRefComponentPropsWithoutRef - 这将接受可以是 "div""button" 或任何其他组件。它将包括 React 特定的道具,例如 className 以及:

import React, {
  forwardRef,
  ComponentPropsWithoutRef,
  ComponentProps,
  ComponentPropsWithRef
} from "react";

const ExampleDivComponent = forwardRef<
  HTMLDivElement,
  ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
  return (
    <div {...props} ref={ref}>
      {children}
    </div>
  );
});

<ExampleDivComponent
  className=""
  style={{ background: "green" }}
  tabIndex={0}
  onTouchStart={() => alert("touched")}
/>;

const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
  children,
  ...props
}) => {
  return <button {...props}>{children}</button>;
};

<ExampleButtonComponent onClick={() => alert("clicked")} />;