【问题标题】:Extending HTML elements in React and TypeScript while preserving props在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props
【发布时间】:2017-04-05 12:11:43
【问题描述】:

我想我无法理解这一点,我已经尝试了大概六次并且总是求助于any...一个组件,然后将其包装在另一个组件中,以便 HTML 道具通过所有内容?本质上是自定义 HTML 元素?例如,类似:

interface MyButtonProps extends React.HTMLProps<HTMLButtonElement> {}
class MyButton extends React.Component<MyButtonProps, {}> {
    render() {
        return <button/>;
    }
} 

interface MyAwesomeButtonProps extends MyButtonProps {}
class MyAwesomeButton extends React.Component<MyAwesomeButtonProps, {}> {
    render() {
        return <MyButton/>;
    }
}

用法:

<MyAwesomeButton onClick={...}/>

每当我尝试这种组合时,都会收到类似于以下内容的错误:

foo 的属性 'ref' 不可分配给目标属性。

【问题讨论】:

  • 您正在寻找高阶组件(组件工厂)。在网上检查一下,看看是否符合您的要求。它们本质上是“组件工厂”,允许您将组件包装在另一个组件中,该组件返回该初始组件,但带有新的或修改的道具。
  • 错误是编译时间(编译时)?因为,我尝试使用tsc 命令编译您的代码并且工作正常。我试图渲染&lt;MyAwesomeButton onClick={() =&gt; console.log('Clicked')}/&gt;
  • 我注意到的一件事是,您不应该将 props 以 &lt;button {...this.props} /&gt; 的形式传递给您的原生 (HTML) 元素吗?
  • 此线程讨论了一些建议的答案github.com/DefinitelyTyped/DefinitelyTyped/issues/36505 的问题,并建议接口 Props 扩展 React.ComponentProps 以捕获任何丢失的道具。
  • 我总是使用React.ComponentsProps&lt;"button"&gt; 通用输入可以是任何东西,从反应组件到字符串,例如“div”。在使用或不使用 ref 转发时,还可以使用变体 ComponentsPropsWithRefComponentsPropsWithoutRef

标签: reactjs typescript tsx


【解决方案1】:

您可以更改组件的定义以允许 react html 按钮道具

class MyButton extends React.Component<MyButtonProps & React.HTMLProps<HTMLButtonElement>, {}> {
    render() {
        return <button {...this.props}/>;
    }
}

这将告诉 typescript 编译器您要输入按钮道具以及“MyButtonProps”

【讨论】:

【解决方案2】:

好像上面的答案已经过时了。

在我的例子中,我用一个功能组件包装了一个样式化的组件,但仍然想公开常规的 HTML 按钮属性。

export const Button: React.FC<ButtonProps &
  React.HTMLProps<HTMLButtonElement>> = ({
  ...props,
  children,
  icon
}) => (
  <StyledButton {...props}>
    {icon && <i className="material-icons">{icon}</i>}
    {children}
  </StyledButton>
);

【讨论】:

  • 我想你想要React.FC&lt;ButtonProps &amp; React.HTMLProps&lt;HTMLButtonElement&gt;&gt;ButtonHTMLAttributes 方法不接受 ref 属性。
  • ...如果要使用 ref 属性,还需要使用 React.forwardRef 包装组件。明白了。
  • 使用 {...props} 作为第一个道具,以减少错误
【解决方案3】:

我总是喜欢这样做:

import React from 'react';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  title: string;
  showIcon: boolean;
}

const Button: React.FC<ButtonProps> = ({ title, showIcon, ...props }) => {
  return (
    <button {...props}>
      {title}
      {showIcon && <Icon/>}
    </button>
  );
};

那么你可以这样做:

<Button
  title="Click me"
  onClick={() => {}} {/* You have access to the <button/> props */}
/>

【讨论】:

  • 重要细节:您需要扩展HTMLAttributes&lt;HTMLButtonElement&gt;,而不是HTMLProps&lt;...&gt;
  • @JosefKufner 答案就是这样做的。
  • 是的,确实如此。但其他答案和问题没有。这个小细节很容易错过,错误信息也无济于事。
【解决方案4】:

这是我在扩展原生元素时所做的:

import React, { ButtonHTMLAttributes, forwardRef } from "react";

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
    myExtraProp1: string;
    myExtraProp2: string;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
    ({ myExtraProp1, myExtraProp2, ...props }, ref) => (
        <button
            {...props}
            ref={ref}
            // Do something with the extra props
        />
    ),
);

Button.displayName = "Button";

forwardRef 确保您在使用组件时可以使用ref 获取对底层 HTML 元素的引用。

【讨论】:

    【解决方案5】:

    我为我解决了这个代码,你只需要从 react 中导入 ButtonHTMLAttributes 就可以了

    import { ButtonHTMLAttributes } from "react";
    
    interface MyButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
        children: any;
    }
    
    export const MyButton = (props: ButtonI) => {
        const { children } = props;
     
        return <button {...props}>{children}</button>;
    };
    

    【讨论】:

      【解决方案6】:
        private yourMethod(event: React.MouseEvent<HTMLButtonElement>): void {
        event.currentTarget.disabled = true;
        }  
      
       <Button
       onClick={(event) => this.yourMethod(event)}
       />
      

      【讨论】:

        【解决方案7】:

        我今天遇到了同样的问题,我是这样解决的:

        ReactButtonProps.ts

        import {
          ButtonHTMLAttributes,
          DetailedHTMLProps,
        } from 'react';
        
        /**
         * React HTML "Button" element properties.
         * Meant to be a helper when using custom buttons that should inherit native "<button>" properties.
         *
         * @example type MyButtonProps = {
         *   transparent?: boolean;
         * } & ReactButtonProps;
         */
        export type ReactButtonProps = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
        

        Button-ish组件中的使用:

        import classnames from 'classnames';
        import React, { ReactNode } from 'react';
        import { ReactButtonProps } from '../../types/react/ReactButtonProps';
        
        type Props = {
          children: ReactNode;
          className?: string;
          mode?: BtnMode;
          transparent?: boolean;
        } & ReactButtonProps;
        
        
        const BtnCTA: React.FunctionComponent<Props> = (props: Props): JSX.Element => {
          const { children, className, mode = 'primary' as BtnMode, transparent, ...rest } = props;
          
          // Custom stuff with props
        
          return (
            <button
              {...rest} // This forward all given props (e.g: onClick)
              className={classnames('btn-cta', className)}
            >
              {children}
            </button>
          );
        };
        
        export default BtnCTA;
        

        用法:

        <BtnCTA className={'test'} onClick={() => console.log('click')}>
          <FontAwesomeIcon icon="arrow-right" />
          {modChatbot?.homeButtonLabel}
        </BtnCTA>
        

        我现在可以使用onClick,因为它是从 ReactButtonProps 扩展而来的,它通过...rest 自动转发到 DOM。

        【讨论】:

          【解决方案8】:

          如果您使用来自“@emotion/styled”的样式化组件,则所有答案都不起作用。

          我必须更深入一点。

          import styled from "@emotion/styled";
          import React, { ButtonHTMLAttributes } from 'react';
          
          export type ButtonVariant = 'text' | 'filled' | 'outlined';
          
          export const ButtonElement = styled.button`
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 12px 16px;
          `;
          
          export interface ButtonProps {
            variant: ButtonVariant;
          }
          export const Button: React.FC<ButtonProps & React.DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>> = ({
            children,
            variant,
            ...props
          }) => (
            <ButtonElement
              {...props}
            >
              {children}
            </ButtonElement>
          );
          
          

          此样式允许您传递按钮具有的所有道具,而且更重要的是,将 {...props} 填充到 ButtonElement 允许您轻松地重用带有样式组件的按钮,以一种好的方式进行您想要的 css 更改

          import { Button } from '@components/Button';
          
          export const MySpecificButton = styled(Button)`
            color: white;
            background-color: green;
          `;
          

          【讨论】:

            【解决方案9】:

            这通过使用类型(而不是接口)对我有用:

            type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
              children: React.ReactNode;
              icon?: React.ReactNode;
            };
            
            function Button({ children, icon, ...props }: ButtonProps) {
              return (
                <button {...props}>
                  {icon && <i className="icon">{icon}</i>}
                  {children}
                </button>
              );
            }
            

            【讨论】:

              【解决方案10】:

              使用 Ref & Key 扩展 HTML 元素

              TL;DR
              如果您需要能够接受 `ref` 和 key,那么您的类型定义将需要使用这个长而丑陋的东西:
              import React, { DetailedHTMLProps, HTMLAttributes} from 'react';
              
              DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
              
              类型定义
              查看类型定义文件,这是类型。我不确定为什么它不短,似乎你总是两次传递相同的 HTMLElement ?
              type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E;
              
              缩短的详细HTMLProps

              您可以创建自己的类型来缩短我们的情况(这似乎是常见情况)。

              import React, { ClassAttributes, HTMLAttributes} from 'react';
              
              type HTMLProps<T> = ClassAttributes<T> & HTMLAttributes<T>;
              
              export interface ButtonProps extends HTMLProps<HTMLButtonElement> {
                variant: 'contained' | 'outlined';
              }
              
              示例组件
              import React, {ClassAttributes, HTMLAttributes, ForwardedRef, forwardRef} from 'react';
              
              type HTMLProps<T> = ClassAttributes<T> & HTMLAttributes<T>;
              
              export interface ButtonProps extends HTMLProps<HTMLButtonElement> {
                variant: 'contained' | 'outlined';
              }
              
              export const Button: React.FC<ButtonProps> = forwardRef(
                (props : ButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
              
                  return (
                    <button key="key is accepted" ref={ref} {...props}>
                      {props.children}
                    </button>
                  );
                },
              );
              

              【讨论】:

                【解决方案11】:

                您可以这样做来扩展按钮属性

                import { ButtonHTMLAttributes, ReactNode } from "react";
                
                interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
                    children: ReactNode;
                }
                
                const Button = ({ children, ...props }: Props): JSX.Element => {
                    return <button {...props}>{children}</button>;
                };
                

                【讨论】:

                  猜你喜欢
                  • 2019-02-09
                  • 2018-05-12
                  • 2019-10-03
                  • 1970-01-01
                  • 2015-06-13
                  • 2019-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-23
                  相关资源
                  最近更新 更多