将子界面用作具有通用界面的道具界面

时间:2019-08-21 07:15:52

标签: reactjs typescript

我正在尝试在React中实现某些类型的输入,从而不断出错。

我的想法是,我有一个枚举(EBreakpoint),该枚举与我们支持的每个设备对应。代理包装器组件将每个设备用作道具,然后将值解析为子组件的道具。

TypeScript部分有效,as I've demonstrated in a Typescript Playground,但是实现不断出现此错误:

JSX element type 'Element[] | IChild<any>' is not a constructor function for JSX elements.
  Type 'Element[]' is missing the following properties from type 'Element': type, props, key

Codesandbox URL:https://codesandbox.io/s/serene-sea-3wmxk(已删除代理的部分功能,以尽可能地隔离问题)

index.tsx

import * as React from "react";
import { render } from "react-dom";
import { Proxy } from "./Proxy";

import "./styles.css";

const ChildElement: React.FC<{ text: string }> = ({ text }) => {
  return <>{text}</>;
};

function App() {
  return (
    <div className="App">
      <Proxy Mobile={{ text: "Mobile" }}>
        <ChildElement text="Default" />
      </Proxy>
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

Proxy.tsx

import * as React from "react";

enum EBreakpoint {
  Mobile = "Mobile",
  Desktop = "Desktop"
}

interface IChild<P> extends React.ReactElement {
  props: P;
}

type TResponsiveProps<P> = { [key in EBreakpoint]?: P };

interface IProps<P> extends TResponsiveProps<P> {
  children: IChild<P>;
}

export function Proxy<P>({ children, ...breakpoints }: IProps<P>) {
  return Object.keys(breakpoints).length && React.isValidElement(children)
    ? Object.keys(breakpoints).map(breakpoint => (
        <div>{React.cloneElement(children, breakpoints[breakpoint])}</div>
      ))
    : children;
}

1 个答案:

答案 0 :(得分:3)

这不是您的代码或JSX中的错误(如注释中所建议)。这是JSX在打字稿中的限制。

使用JSX,您的所有代码都像

<Proxy Mobile={{ text: "Mobile" }}>
  <ChildElement text="Default" />
</Proxy>

将被编译为

React.createElement(Proxy, {
  Mobile: {
    text: "Mobile"
  }
}, React.createElement(ChildElement, {
  text: "Default"
}));

因此,所有子级将作为JSX.Element传递到父级组件。此界面是黑盒,无法检索到类似prop类型的属性:

  

The JSX result type

     

默认情况下,JSX表达式的结果键入为any。您可以通过指定JSX.Element接口来自定义类型。但是,无法从此接口检索有关JSX的元素,属性或子级的类型信息。这是一个黑匣子。

目前有一个issue可供使用,其中描述了如何概括JSX.Element以支持您的用例的路线图,但没有明确的时间表,何时何地,在何种程度上或是否会降落。

不过,有一种快速的解决方法来仍然具有您所需的功能,方法是专门声明代理的通用类型,如下所示: 请注意,这不会执行类型检查,以确保Proxy和子级上的两个接口匹配。这是不可能的(如上所述)

enum EBreakpoint {
  Mobile = "Mobile",
  Desktop = "Desktop"
}

type TResponsiveProps<P> = { [key in EBreakpoint]?: P };

type IProps<P> = TResponsiveProps<P> & { children?: React.ReactNode };

export function Proxy<P>({
  children,
  ...breakpoints
}: IProps<P>): React.ReactElement | null {
  return Object.keys(breakpoints).length && React.isValidElement(children) ? (
    <React.Fragment>
      {Object.keys(breakpoints).map(breakpoint => (
        <div>{React.cloneElement(children, breakpoints[breakpoint])}</div>
      ))}
    </React.Fragment>
  ) : (
    <React.Fragment>{children}</React.Fragment>
  );
}

,然后在您的index.tsx中像这样:

<Proxy<{ text: string }> Mobile={{ text: "Mobile" }}>
  <ChildElement text="Default" />
</Proxy>

当然,您也可以将道具类型{ text: string }提取到另一个接口中以供重用。