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

我只是无法理解这一点,我想,我可能已经尝试了6次,总是求助于 any... ... 有没有一种合法的方法可以从一个 HTML 元素开始,把它包装在一个组件中,然后再把它包装到另一个组件中,这样 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={...}/>

Whenever I attempt this sort of composition, I get an error similar to:

Foo 的“ ref”属性不能分配给目标属性。

140176 次浏览

您可以更改组件的定义,以允许使用 response html 按钮支持

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

That will tell the typescript compiler that you want to enter the button props along with 'MyButtonProps'

看来上面的答案已经过时了。

在我的例子中,我用一个函数组件包装一个样式化的组件,但是仍然希望公开常规的 HTML 按钮属性。

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

我总是喜欢这样做:

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 */}
/>
  private yourMethod(event: React.MouseEvent<HTMLButtonElement>): void {
event.currentTarget.disabled = true;
}


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

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

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。

我为我解决这个代码,你只需要从反应导入 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>;
};

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

import { ButtonHTMLAttributes, ReactNode } from "react";


interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
}


const Button = ({ children, ...props }: Props): JSX.Element => {
return <button {...props}>{children}</button>;
};

这就是我在扩展本机元素时要做的事情:

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

如果你使用的是来自“@motion/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>
);


这个样式允许你传递按钮所有的道具,而且不仅如此,在 ButtonElement 中填充{ ... props }可以让你轻松地重用带样式组件的 Button,以一种更好的方式做你想要的 css 改变

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


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

通过使用类型(而不是接口) ,这种方法对我很有效:

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

使用引用和键扩展 HTML 元素

DR
If you need to be able to accept `ref` and key then your type definition will need to use this long ugly thing:
import React, { DetailedHTMLProps, HTMLAttributes} from 'react';


DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
Type Definition
查看类型定义文件,这是类型。我不知道为什么它不更短,似乎你总是通过相同的 HTML 元素两次?
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>
);
},
);
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>
);
}
import {  FC, HTMLProps } from 'react';


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

您需要扩展您的接口。

import {ButtonHTMLAttributes, ReactNode} from "react";


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