在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props

问题描述 投票:0回答:16

我想我就是无法理解这个问题,我已经尝试了大概六次并且总是求助于

any
...有没有一种合法的方法可以从 HTML 元素开始,将其包装在组件中,并将其包装在另一个组件中,以便 HTML props 穿过所有内容?本质上是自定义 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”不可分配给目标属性。

reactjs typescript tsx
16个回答
180
投票

我总是喜欢这样做:

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

156
投票

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

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

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


67
投票

似乎上面的答案已经过时了。

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

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

11
投票

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

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

8
投票

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

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 元素的引用。


8
投票

最简洁的方法

这是我每次想要扩展 HTML 元素时使用的方法。

import { JSX } from 'react';

type ButtonProps = JSX.IntrinsicElements['button']
type DivProps = JSX.IntrinsicElements['div']

5
投票
import * as React from "react";

interface Props extends React.HTMLProps<HTMLInputElement> {
  label?: string;
}

export default function FormFileComponent({ label, ...props }: Props) {
  return (
    <div>
      <label htmlFor={props?.id}></label>
      <input type="file" {...props} />
    </div>
  );
}

4
投票

使用 Ref 和 Key 扩展 HTML 元素

TL;博士
如果您需要能够接受“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 = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {

    return (
      <button key="key is accepted" ref={ref} {...props}>
        {props.children}
      </button>
    );
  },
);

3
投票

如果您使用“@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 可以让您轻松地使用样式组件重用 Button,以一种好的方式进行您想要的 css 更改

import { Button } from '@components/Button';

export const MySpecificButton = styled(Button)`
  color: white;
  background-color: green;
`;

3
投票

您需要扩展您的接口。

import {ButtonHTMLAttributes, ReactNode} from "react";

export interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>{
    appearance: 'primary' | 'ghost';
    children: ReactNode;
}

2
投票

我帮我解决了这个代码,你只需要从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>;
};

1
投票
import {  FC, HTMLProps } from 'react';

const Input: FC<HTMLProps<HTMLInputElement>> = (props) => {
  return <input {...props} />;
};

0
投票
  private yourMethod(event: React.MouseEvent<HTMLButtonElement>): void {
  event.currentTarget.disabled = true;
  }  

 <Button
 onClick={(event) => this.yourMethod(event)}
 />

0
投票

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

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。


0
投票

我让这个工作了。不知何故,使用

HtmlHTMLAttributes
扩展道具对我来说不起作用。有效的是使用
ComponentPropsWithoutRef
,如下所示:

import clsx from "clsx"
import { FC } from "react";

 interface InputProps
  extends React.ComponentPropsWithoutRef<'input'>{
    className?: string;
  }

const Input: FC<InputProps> = ({ className, ...props}) => {
  return (
    <input
    className={clsx('border-solid border-gray border-2 px-6 py-2 text-lg rounded-3xl w-full', className)}
     {...props} />
  )
}

export default Input

然后我可以使用通常的输入道具:

              <Input
                required
                placeholder="First Name"
                value={formState.firstName}
                className="border-solid border-gray border-2 px-6 py-2 text-lg rounded-3xl w-full"
                onChange={(e) =>
                  setFormState((s) => ({ ...s, firstName: e.target.value }))
                }
              />
   

0
投票

任何元素的简单方法

interface YOUR_INTERFACE_NAME extends YOUR_ELEMENT_TYPE{
props which you want to add or use in other elements
}

输入示例

interface MyInput extends InputHTMLAttributes<HTMLInputElement> {
name: string;
label: string;
...
}

按钮示例

interface MyButton extends ButtonHTMLAttributes<HTMLButtonElement> {
name: string;
label: string;
...
}

对所有其他元素使用相同的模式——从 React 导入这些类型或仅添加 REACT。在他们面前

© www.soinside.com 2019 - 2024. All rights reserved.