【问题标题】:Use child interface as prop interface with generic interface使用子接口作为带有通用接口的道具接口
【发布时间】:2019-08-21 07:15:52
【问题描述】:

我正在尝试在 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;
}

【问题讨论】:

  • 这是 JSX 中的某种错误,尽管接口没有被完全解析?

标签: reactjs typescript


【解决方案1】:

这不是您的代码或 JSX 中的错误(如 cmets 中所建议的)。这是 JSX 在 typescript 中的一个限制。

使用 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 }提取到另一个接口中重复使用。

【讨论】:

  • 感谢您的详尽解释。我很遗憾这不仅仅是我可以修复的代码中的错误。但至少我现在知道问题出在哪里了。
  • 我已经包含了一个编辑,它使您仍然可以使您的代理工作,但在使用 Proxy 组件时需要一些打字工作
  • 谢谢。我实际上在本地修复了丢失的片段问题,但这是我正在寻找的类型检查。再次感谢您。
猜你喜欢
  • 1970-01-01
  • 2023-02-21
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 2019-10-12
  • 2022-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多