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

时间:2016-10-13 23:22:52

标签: reactjs typescript

针对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<a>的未知支柱。针对此示例的案例的建议解决方案是使用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支持。我们有更好的方法吗?

6 个答案:

答案 0 :(得分:4)

您可能无法避免使用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;
    }
}

使用LinkPropsKeys对象(需要匹配LinkProps)将帮助您保持接口和运行时查找之间的密钥同步。

答案 1 :(得分:1)

我接受了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支持对象休息/传播,我就可以查找rest()的所有用法并将其简化为const {a, b, c, ...htmlProps} = props

答案 2 :(得分: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
}

希望这会有所帮助。

答案 3 :(得分:1)

使用...rest

type ButtonProps = {
    disabled: boolean;
};

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

答案 4 :(得分:0)

上面示例中的React.HtmlAttributes现在是通用的,因此我需要从React.AnchorHTMLAttributes<HTMLAnchorElement>扩展。

示例:

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 :(得分:0)

这样的吸气剂可以工作:

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" />