我正在尝试在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;
}
答案 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 }
提取到另一个接口中以供重用。