React 中休息道具的 TypeScript 解决方法

Posted

技术标签:

【中文标题】React 中休息道具的 TypeScript 解决方法【英文标题】:TypeScript workaround for rest props in React 【发布时间】:2017-02-23 06:17:07 【问题描述】:

针对 TypeScript 2.1 更新

TypeScript 2.1 now supports object spread/rest,因此不再需要解决方法!


原始问题

TypeScript 支持 JSX spread attributes,它在 React 中常用来将 html 属性从组件传递到呈现的 HTML 元素:

interface LinkProps extends React.HTMLAttributes 
  textToDisplay: string;


class Link extends React.Component<LinkProps, > 
  public render():JSX.Element 
    return (
      <a ...this.props>this.props.textToDisplay</a>
    );
  


<Link textToDisplay="Search" href="http://google.com" />

然而,React 引入了warning if you pass any unknown props to an HTML element。上面的例子会产生一个 React 运行时警告 textToDisplay&lt;a&gt; 的一个未知属性。对于像这个例子这样的案例,建议的解决方案是使用object rest properties 提取您的自定义道具并将其余部分用于 JSX 传播属性:

const textToDisplay, ...htmlProps = this.props;
return (
  <a ...htmlProps>textToDisplay</a>
);

但是 TypeScript 还不支持这种语法。我知道希望有一天we will be able to do this in TypeScript。更新:TS 2.1 now supports object spread/rest!你为什么还在读这个??)解决方法?我正在寻找一种不会影响类型安全的解决方案,并且发现它非常困难。例如我可以这样做:

const customProps = ["textDoDisplay", "otherCustomProp", "etc"];
const htmlProps:HTMLAttributes = Object.assign(, this.props);
customProps.forEach(prop => delete htmlProps[prop]);

但这需要使用未针对实际道具进行验证的字符串属性名称,因此容易出现拼写错误和不良的 IDE 支持。有没有更好的方法可以做到这一点?

【问题讨论】:

请注意,您正在寻找的语法现在可用 @KyleGobel 真的,我很高兴。 :) 这个问题应该被删除吗? “TypeScript 2.1 现在支持对象传播/休息,因此不再需要变通方法!”你怎么用这个?!! @gyozokudor 只需使用 TypeScript 2.1 或更高版本(现在最高 3.4!)并使用原始示例:const textToDisplay, ...htmlProps = this.props 【参考方案1】:

这实际上比上面所有的答案都容易。您只需要按照以下示例进行操作:

type Props = 
  id: number,
  name: string;
  // All other props
  [x:string]: any;


const MyComponent:React.FC<Props> = props => 
  // Any property passed to the component will be accessible here

希望这会有所帮助。

【讨论】:

谢谢,但我认为您误解了错误。这不是 TypeScript 编译错误,而是由于将(通过 JSX 扩展语法)属性传递给无法识别的 HTML 元素而导致的 React 运行时错误,例如上面示例中 &lt;a&gt; 标记上的 textToDisplay。无论如何,TS 2.1 早就解决了这个问题,增加了对对象休息/扩展语法的支持。 这正是我想要的!谢谢 完美。谢谢!!【参考方案2】:

您可能无法避免使用this.props 的属性子集创建一个新对象,但您可以通过类型安全来做到这一点。

例如:

interface LinkProps 
    textToDisplay: string;


const LinkPropsKeys: LinkProps =  textToDisplay: "" ;

class Link extends React.Component<LinkProps & React.HTMLAttributes, > 
    public render(): JSX.Element 
        return (
            <a  ...this.getHtmlProps() > this.props.textToDisplay </a>
        );
    

    private getHtmlProps(): React.HTMLAttributes 
        let htmlProps =  as React.HTMLAttributes;

        for (let key in this.props) 
            if (!(LinkPropsKeys as any)[key]) 
                htmlProps[key] = this.props[key];
            
        

        return htmlProps;
    

使用需要匹配LinkPropsLinkPropsKeys 对象将帮助您保持界面和运行时查找之间的键同步。

【讨论】:

很好的建议,这很有帮助。【参考方案3】:

使用...rest

type ButtonProps = 
    disabled: boolean;
;

function Button(props: ButtonProps): JSX.Element 
    const disabled = false, ...rest = props;
...
return (
    <button disabled=disabled ...rest>
....

【讨论】:

但是您如何真正推断传递给button 的内容实际上是有效的HTML 道具或事件?【参考方案4】:

上面示例中的 React.HtmlAttributes 现在是通用的,所以我需要从 React.AnchorHTMLAttributes&lt;HTMLAnchorElement&gt; 扩展。

例子:

import React from 'react';

type  AClickEvent = React.MouseEvent<HTMLAnchorElement>;

interface LinkPropTypes extends React.AnchorHTMLAttributes<HTMLAnchorElement> 
    to: string;
    onClick?: (x: AClickEvent) => void;


class Link extends React.Component<LinkPropTypes> 
  public static defaultProps: LinkPropTypes = 
    to: '',
    onClick: null,
  ;

private handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => 
   ...
    event.preventDefault();
    history.push(this.props.to);
 ;

  public render() 
    const  to, children, ...props  = this.props;
    return (
      <a href=to ...props onClick=this.handleClick>
        children
      </a>
    );
    


export default Link;

【讨论】:

【参考方案5】:

这样的吸气剂可以工作:

class Link extends React.Component<
  textToDisplay: string;
 & React.HTMLAttributes<HTMLDivElement>> 

  static propTypes = 
    textToDisplay: PropTypes.string;
  

  private get HtmlProps(): React.HTMLAttributes<HTMLAnchorElement> 
    return Object.fromEntries(
      Object.entries(this.props)
      .filter(([key]) => !Object.keys(Link.propTypes).includes(key))
    );
  

  public render():JSX.Element 
    return (
      <a ...this.HtmlProps>
        this.props.textToDisplay
      </a>
    );
  


<Link textToDisplay="Search" href="http://google.com" />

【讨论】:

【参考方案6】:

我接受了 Nitzen Tomer 的回答,因为这是我所追求的基本理念。

作为一个更通用的解决方案,这是我最终采用的解决方案:

export function rest(object: any, remove: [key: string]: any) 
  let rest = Object.assign(, object);
  Object.keys(remove).forEach(key => delete rest[key]);
  return rest;

所以我可以这样使用它:

const a, b, c = props;
const htmlProps = rest(props, a, b, c);

一旦 TypeScript 支持 object rest/spread,我就可以查找 rest() 的所有用法并将其简化为 const a, b, c, ...htmlProps = props

【讨论】:

【参考方案7】:

如果您将 ...rest 作为参数传递给组件,TypeScript 现在会忽略它。在我看来 ...rest 参数不需要类型安全,因为这些是由父组件传递给子组件的默认参数。例如 redux 将有关 store 的信息传递给子组件,因此 ...rest 参数始终存在并且不需要类型安全或 propTypes。

//here's the right solution

interface schema
  loading: boolean

//pass ...rest as argument
export function MyComponent(loading, ...rest:schema)
  if (loading) return <h2>Loading ...</h2>
  return (
    <div ...rest>
      <h2>Hello World </h2>
    </div>

强文本

【讨论】:

以上是关于React 中休息道具的 TypeScript 解决方法的主要内容,如果未能解决你的问题,请参考以下文章

在没有道具的 Typescript 中使用 React.forwardRef

Eslint:功能组件中的默认道具问题(Typescript - React)

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

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

React Typescript 类组件默认道具接口

“警告:列表中的每个子项在 React (TypeScript) 中都应该有一个唯一的“键”道具”