【问题标题】:React and Typescript functional component can be 1 of 3 interfacesReact 和 Typescript 功能组件可以是 3 个接口之一
【发布时间】:2020-04-29 17:20:13
【问题描述】:

我正在尝试创建一个可以使用 3 个接口中的 1 个接口的组件,该组件能够根据传递给它的 props 来确定哪个接口。

interface CommonProps {
  label: string;
  icon?: React.ComponentType;
  role?: string;
}

interface ButtonProps extends CommonProps {
  handleOnClick: () => void;
  selected?: boolean;
  largeVariant?: boolean;
}

interface LinkProps {
  to: string;
  openInNewTab?: boolean;
}

interface HrefProps {
  href: string;
  openInNewTab?: boolean;
}

const Button: React.FC<ButtonProps | LinkProps | HrefProps> = props => {
  const { label, handleOnClick, to, href, icon, openInNewTab } = props;
  if (to || href) {
    const Component = to ? Link : 'a';
    return (
      <StyledButton
        component={Component}
        target={openInNewTab ? '_blank' : undefined}
        onMouseDown={(e: any) => {
          href && pushMatomoExternalLink(e, href);
        }}
        {...props}
      >
        {icon && <StyledIcon icon={icon} />}
        {label}
      </StyledButton>
    );
  }
  return (
    <StyledButton onClick={handleOnClick} {...props}>
      {icon && <StyledIcon icon={icon} />}
      {label}
    </StyledButton>
  );
};

期望的行为,包括我希望看到的错误。

<Button label="View Report" handleOnClick={action('BUTTON CLICKED')} />

会推断接口是ButtonProps

<Button label="View Report" selected />

TypeScript 错误:类型“{”中缺少属性“handleOnClick” 标签:字符串; selected: boolean;}' 但在 'ButtonProps' 类型中是必需的。

<Button label="View Report" openInNewTab />

会推断该接口是 LinkProps 或 HrefProps

类型 '{ label: string; 中缺少属性 'to' openInNewTab:布尔值; }' 但在“LinkProps”类型中是必需的。

类型'{ label: string; 中缺少属性'href'; openInNewTab:布尔值; }' 但在 'HrefProps' 类型中是必需的。

<Button label="View Report" href="/" openInNewTab />

会推断出接口是HrefProps

【问题讨论】:

  • 请考虑编辑代码以构成一个minimal reproducible example,该minimal reproducible example 可以拖放到The Playground 等独立IDE 中,以便其他人可以看到您的问题。现在我没有LinkStyledButton 的定义,所以我不确定我的建议是否适合你。我也不明白您所说的“推断”是什么意思;您希望编译器在哪里推断接口?编译器在这里的行为在我看来是合理的;您能否详细说明您的预期与正在发生的事情?
  • 感谢您的建议。下班后我会对示例进行一些更改。

标签: reactjs typescript


【解决方案1】:

我不明白你的问题是什么;如果我采用您的确切代码,除了 Button 的错误实现(假设 minimum reproducible example 不需要此实现),编译器会给出您想要查看的错误

declare const Button: React.FC<ButtonProps | LinkProps | HrefProps>;
<Button label="View Report" handleOnClick={console.log} />;
// okay

<Button label="View Report" selected />; 
// Property 'handleOnClick' is missing in type '{ label: string; selected: true; }' 
// but required in type 'ButtonProps'.(2322)

<Button label="View Report" openInNewTab />;
// Property 'href' is missing in type '{ label: string; openInNewTab: true; }' 
// but required in type 'HrefProps'.(2322)

<Button label="View Report" href="/" openInNewTab />;
// okay

如果这不是您所期望的,或者问题实际上与Button 的实施有关,那么请编辑您的问题以说明您的问题是什么,包括一个可重现的示例,该示例准确地显示了您所看到的内容以及如何它与您期望看到的不同。祝你好运!

Playground link to code

【讨论】:

    【解决方案2】:

    一种可能的解决方案是使用type guards 来检查传递的道具类型。类型守卫应该有一个逻辑来找出它得到了什么类型的传递道具。在下面的代码中,propsIsButtonPropspropsIsLinkPropspropsIsHrefProps 是类型保护。

    我已经简化了你的代码来展示主要思想。

    function propsIsButtonProps (props: any): props is ButtonProps {
      return props.selected !== undefined;
    }
    function propsIsLinkProps (props: any): props is LinkProps {
      return props.to !== undefined;
    }
    function propsIsHrefProps (props: any): props is HrefProps {
      return props.href !== undefined;
    }
    
    export const Button: React.FC<ButtonProps | LinkProps | HrefProps> = props => {
      if (propsIsLinkProps(props) || propsIsHrefProps(props)) {
        const Component = propsIsLinkProps(props) ? 'div' : 'a';
        return (
          <Component
            target={props.openInNewTab ? '_blank' : undefined}
            onMouseDown={(e: any) => {
              propsIsHrefProps(props) && pushMatomoExternalLink(e, props.href);
            }}
            {...props}
          >
            {props.label}
          </Component>
        );
      }
      return (
        <button onClick={props.handleOnClick} {...props}>
          {props.label}
        </button>
      );
    };
    

    工作演示是here

    【讨论】:

      【解决方案3】:

      你说的是函数重载,对吧?这可以用 C / C++ 等语言轻松完成,但使用 typescript 有点困难。这是因为最终将 typescript 代码转换为 JavaScript 并且在 JavaScript 中函数重载是不可能的。

      这是该场景的可能解决方案。

      interface CommonProps {
        label: string;
        icon?: string;
        role?: string;
      }
      
      export interface ButtonProps extends CommonProps {
        kind: "button-props";
        handleOnClick: () => void;
        selected?: boolean;
        largeVariant?: boolean;
      }
      
      export interface LinkProps {
        kind: "link-props";
        to: string;
        openInNewTab?: boolean;
      }
      
      export interface HrefProps {
        kind: "href-props";
        href: string;
        openInNewTab?: boolean;
      }
      
      type Types = HrefProps | LinkProps | ButtonProps ;
      
      const Button : React.FC<Types> = props => {
          switch(props.kind){
              case "button-props" :
                  // some code
              case "href-props" :
                  // some code
              case "link-props" :
                  // some code
              default :
                  return;
          }
      }
      

      现在在你的父组件中,

      import { ButtonProps, LinkProps, HrefProps } from './Button.component.ts';
      
      function Parent = (props) => {
         const propsToChild : ButtonProps = {// data object with type ButtonProps}
         return (
           <Button {...propsToChild}/>
         )
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-18
        • 2020-08-14
        • 2018-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-20
        • 2021-10-26
        • 2018-12-01
        相关资源
        最近更新 更多