在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props
Posted
技术标签:
【中文标题】在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props【英文标题】:Extending HTML elements in React and TypeScript while preserving 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
命令编译您的代码并且工作正常。我试图渲染<MyAwesomeButton onClick=() => console.log('Clicked')/>
我注意到的一件事是,您不应该将 props 以 <button ...this.props />
的形式传递给您的原生 (HTML) 元素吗?
此线程讨论了一些建议的答案github.com/DefinitelyTyped/DefinitelyTyped/issues/36505 的问题,并建议接口 Props 扩展 React.ComponentProps 以捕获任何丢失的道具。
我总是使用React.ComponentsProps<"button">
通用输入可以是任何东西,从反应组件到字符串,例如“div”。在使用或不使用 ref 转发时,还可以使用变体 ComponentsPropsWithRef
和 ComponentsPropsWithoutRef
。
【参考方案1】:
您可以更改组件的定义以允许 react html 按钮道具
class MyButton extends React.Component<MyButtonProps & React.HTMLProps<HTMLButtonElement>, >
render()
return <button ...this.props/>;
这将告诉 typescript 编译器您要输入按钮道具以及“MyButtonProps”
【讨论】:
这不是和 OP 正在做的完全一样,只是语法不同吗?我认为从React.HTMLProps
扩展道具与使用&
运算符具有相同的效果。就我而言,这没有什么区别,两种语法都会出现相同的编译器错误。
这会产生 TS 错误:类型“字符串”不可分配给类型“按钮”| “提交” | “重置” |未定义'typescriptlang.org/play/?jsx=2#code/…
@AlexM 看起来界面自 3 年前以来已经发生了一些变化……这行得通。 typescriptlang.org/play/index.html?jsx=2#code/…
过时了。我收到 HTMLInput 元素上的事件的类型错误。 ***.com/a/61247412/7886229 工作。
如果您遇到该类型错误,而不是 React.HTMLProps
,请尝试 React.HTMLAttributes
或此处提到的其他解决方案之一:github.com/DefinitelyTyped/DefinitelyTyped/issues/36505【参考方案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<ButtonProps & React.HTMLProps<HTMLButtonElement>>
。 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<HTMLButtonElement>
,而不是HTMLProps<...>
。
@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>;
;
【讨论】:
以上是关于在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript:为样式化的组件元素扩展 React 组件道具
使用 airbnb 和 prettier 扩展的 ESLint 配置,用于使用 typescript、jest 和 react-hooks 的 react 项目
使用 TypeScript 3 扩展道具的 React 组件中的默认属性值
如何使用具有多个页面和入口点的 React 和 TypeScript 设置 chrome 扩展?