React中其余道具的TypeScript解决方法

ergxz8rk  于 2022-11-26  发布在  TypeScript
关注(0)|答案(9)|浏览(177)

针对TypeScript 2.1更新

TypeScript 2.1现在支持对象扩展/静止,因此不再需要任何变通办法!

原始问题

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 spread属性:

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

但是TypeScript还不支持这种语法。我知道希望有一天we will be able to do this in TypeScript。(更新:TS 2.1现在支持对象扩展/静止!为什么你还在阅读这篇文章?*)同时,有什么解决方法吗?我正在寻找一种不损害类型安全的解决方案,但发现它非常困难。例如,我可以这样做:

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

但是这需要使用字符串属性名,而这些属性名没有经过实际属性的验证,因此容易出现拼写错误和糟糕的IDE支持。有没有更好的方法可以做到这一点?

2uluyalo

2uluyalo1#

实际上,它比上面所有的答案都简单,你只需要按照下面的例子来做:

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
}

希望这对你有帮助。

cunj1qz1

cunj1qz12#

您可能无法避免使用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匹配)将帮助您保持接口和运行时查找之间的键同步。

ljsrvy3e

ljsrvy3e3#

上面示例中的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;
ubby3x7f

ubby3x7f4#

使用...rest

type ButtonProps = {
    disabled: boolean;
};

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

yshpjwxd5#

像这样的吸气剂可以工作:

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

lymgl2op6#

对于那些可能不能很快理解[x:string]: any;在可接受答案中的作用的人:虽然它很像数组的语法,但它确实指定了一个对象,它的键是string类型,它的值是any类型,在TypeScript的术语中,它被称为“Index Signature“。
但是,还要注意,有时候,作为一种替代的、对类型不太宽松的解决方案,您正在使用的库可能也导出了类型,因此您可以使用这些类型。
例如,在扩展Ant的按钮时,可以执行以下操作:

import { ReactNode } from "react";
import { Button as AntButton } from "antd";
import { NativeButtonProps } from "antd/lib/button/button";

interface IButtonProps {
  children?: ReactNode;
}

const Button = ({
  children,
  ...rest
}: IButtonProps & NativeButtonProps): JSX.Element => {
  return <AntButton {...rest}>{children}</AntButton>;
};

export default Button;

注意1:IButtonProps & NativeButtonProps中的&操作符只是在TypeScript中“合并”类型。现在,您不会失去Ant Button属性在您自己的Button上的智能,因为您不再使用any。Ant Button的类型和您的IButtonProps是组合的,因此两者都存在。
**注意2:**您可能也想知道我在哪里找到这个型别。这个型别是汇出到这里:https://github.com/ant-design/ant-design/blob/master/components/button/button.tsx#L124而且它的包含路径也可以使用intellisense实现,只要开始输入NativeButton...它一定会向你建议。

guicsvcw

guicsvcw7#

我接受了尼岑·托默的答案,因为这是我的基本想法。
作为一个更一般化的解决方案,我最终选择了:

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

zf2sa74q

zf2sa74q8#

如果你把...rest参数作为参数传递给你的组件,TypeScript现在会忽略它。在我看来...rest参数不需要类型安全,因为它们是由父组件传递给子组件的默认参数。例如redux将有关存储的信息传递给子组件,所以...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>
}

强文本

0wi1tuuw

0wi1tuuw9#

React.ComponentPropsWithoutRef/React.ComponentPropsWithoutRef
https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/中所述

interface Props extends React.ComponentPropsWithoutRef<"button"> {
  // ...
}
    
const FancyButton = (props: Props) => {
  const { /**/ , ...rest} = props
      
  // ...
      
  return <button {...rest}>{/**/}</button>
}

如果使用forwardRef,请改用React.ComponentPropsWithRef
DEMO

相关问题