在 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 命令编译您的代码并且工作正常。我试图渲染&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 【参考方案1】:

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

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

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

【讨论】:

这不是和 OP 正在做的完全一样,只是语法不同吗?我认为从React.HTMLProps 扩展道具与使用&amp; 运算符具有相同的效果。就我而言,这没有什么区别,两种语法都会出现相同的编译器错误。 这会产生 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&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>;
;

【讨论】:

以上是关于在 React 和 TypeScript 中扩展 HTML 元素,同时保留 props的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript:为样式化的组件元素扩展 React 组件道具

使用 airbnb 和 prettier 扩展的 ESLint 配置,用于使用 typescript、jest 和 react-hooks 的 react 项目

使用 TypeScript 3 扩展道具的 React 组件中的默认属性值

如何使用具有多个页面和入口点的 React 和 TypeScript 设置 chrome 扩展?

如何在 React Native 的无状态功能组件中扩展原生组件的 props TypeScript 接口?

React/Typescript /VScode - 导入路径不能以“.tsx”扩展名结尾