根据也作为prop传递的组件来推断prop的类型

时间:2019-11-26 15:24:45

标签: reactjs typescript

是否可以从作为道具传递的未知组件中推断道具的正确类型?

在已知组件(当前文件中存在)的情况下,我可以获得道具:

type ButtonProps = React.ComponentProps<typeof Button>;

但是,如果我想创建一个通用组件Box,该通用组件接受as道具中的组件,并接受props道具中的组件的道具。该组件可以添加一些默认道具,具有某些行为,没关系。基本上与高阶组件相似,但动态。

import React from "react";

export interface BoxProps<TComponent> {
  as?: TComponent;
  props?: SomehowInfer<TComponent>; // is it possible?
}

export function Box({ as: Component, props }: BoxProps) {
  // Note: it doesn't have to be typed within the Box (I can pass anything, I can control it)
  return <Component className="box" title="This is Box!" {...props} />;
}

function MyButton(props: {onClick: () => void}) {
  return <button className="my-button" {...props} />;
}

// usage:

function Example() {
  // I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type. 
  return (
    <div>
      <Box
        as={MyButton}
        props={{
          onClick: () => {
            console.log("clicked");
          }
        }}
      >
        Click me.
      </Box>
    </div>
  );
}

要求:

  • 必须在不传递泛型类型的情况下工作(可能吗?),因为它几乎可以在任何地方使用
  • 必须使用用户定义的组件(React.ComponentType<Props>
  • 如果它还可以与react html元素(a,按钮,链接等...具有不同的道具)一起使用,那将是很棒的,但不是必需的

1 个答案:

答案 0 :(得分:2)

您可以使用预定义的反应类型ComponentProps从组件类型中提取道具类型。

import React from "react";

export type BoxProps<TComponent extends React.ComponentType<any>> = {
  as: TComponent;
  props: React.ComponentProps<TComponent>;
}

export function Box<TComponent extends React.ComponentType<any>>({ as: Component, props }: BoxProps<TComponent>) {  
  return <div className="box" title="This is Box!">
    <Component  {...props} />;
  </div>
}

function MyButton(props: {onClick: () => void}) {
  return <button className="my-button" {...props} />;
}

// usage:

function Example() {
  // I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type. 
  return (
    <div>
      <Box
        as={MyButton}
        props={{ onClick: () => { } }}
      ></Box>
    </div>
  );
}

Playground Link

根据您的确切用例,解决方案可能会有所不同,但是基本思想是相似的。例如,您可以稍微旋转一下类型,然后将props作为BoxProps的type参数。这样,您可以约束组件props具有可以在Box组件内部提供的某些特定属性:

export type BoxProps<TProps extends {title: string}> = {
  as: React.ComponentType<TProps>;
} & {
  props: Omit<TProps, 'title'>;
}

export function Box<TProps extends {title: string}>({ as: Component, props }: BoxProps<TProps>) {  
  return <div className="box" title="This is Box!">
    <Component title="Title from box" {...props as TProps} />;
  </div>
}

Playground Link

如果要使用固有标记,还可以将keyof JSX.IntrinsicElements添加到TComponent约束中:

export type BoxProps<TComponent extends React.ComponentType<any> | keyof JSX.IntrinsicElements> = {
  as: TComponent;
  props: React.ComponentProps<TComponent>;
}

export function Box<TComponent extends React.ComponentType<any>| keyof JSX.IntrinsicElements>({ as: Component, props }: BoxProps<TComponent>) {  
  return <div className="box" title="This is Box!">
    <Component  {...props} />;
  </div>
}

Playground Link