简体   繁体   English

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

[英]Specify specific props and accept general HTML props in Typescript React App

I have a React Wrapper Component, that accepts some props, but forwards all others to the child component (especially relevent for native props like className, id, etc.).我有一个 React Wrapper 组件,它接受一些道具,但将所有其他道具转发给子组件(特别是与 className、id 等本机道具相关)。

Typescript complains, however, when I pass native props.然而,当我传递原生道具时,Typescript 会抱怨。 See error message:查看错误信息:

TS2339: Property 'className' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< { children?: ReactNode; TS2339:属性“className”不存在于类型“IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< { children?: ReactNode; }> & Readonly< WrapperProps>'. }> & Readonly<WrapperProps>'。

How can I get a component with specific props that also accepts native props ( without accepting any props and giving up on type checking)?如何获得具有特定道具的组件,该组件也接受本机道具(接受任何道具并放弃类型检查)?

My code looks like this:我的代码如下所示:

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

FYI: I found a similar question here, but the answer basically gives up type checking, which I want to avoid: Link to SO-Question仅供参考:我在这里发现了一个类似的问题,但答案基本上放弃了类型检查,我想避免这种情况: Link to SO-Question

We can have a look at how div props are defined:我们可以看看div props 是如何定义的:

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

If we use React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> as the base type we will have all properties of div .如果我们使用React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>作为基本类型,我们将拥有div所有属性。 Since DetailedHTMLProps just adds ref to React.HTMLAttributes<HTMLDivElement> we can use just this as the base interface to get all div properties:由于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
}

JSX.IntrinsicElements has this info, eg 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}`} />

discovered this in the react-typescript-cheatsheet project.react-typescript-cheatsheet项目中发现了这一点。

A co-worker of mine figured it out.我的一个同事想通了。 Sharing here for broader visibility:在这里分享以获得更广泛的可见性:

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

Have a look at ComponentProps , ComponentPropsWithRef , and ComponentPropsWithoutRef - this will accept a generic input that can be "div" , "button" , or any other component.看看ComponentPropsComponentPropsWithRefComponentPropsWithoutRef - 这将接受一个通用输入,可以是"div""button"或任何其他组件。 It will include react specific props such as className as well:它还将包括反应特定的道具,例如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")} />;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM