在 Typescript React App 中指定特定props并接受通用 HTML props

IT技术 javascript reactjs typescript react-props
2021-05-02 20:46:53

我有一个 React Wrapper 组件,它接受一些props,但将所有其他props转发给子组件(尤其是与 className、id 等本机props相关的props)。

然而,当我传递原生props时,Typescript 会抱怨。查看错误信息:

TS2339:属性“className”不存在于类型“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>
}

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

4个回答

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

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

如果我们使用React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>作为基本类型,我们将拥有div. 由于DetailedHTMLProps只是添加refReact.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
}

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项目中发现了这一点

我的一个同事想通了。在这里分享以获得更广泛的可见性:

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} />
  }
}

看一看ComponentPropsComponentPropsWithRefComponentPropsWithoutRef-这将接受通用输入,可以是"div""button"或任何其他组件。它将包括react特定的props,例如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")} />;