reactjs - Bruk barnegrensesnitt som prop-grensesnitt med generisk grensesnitt
Jeg prøver å implementere noen typings i React som stadig får en feil.
Ideen er at jeg har en enum (EBreakpoint) som er nøkklet med hver enhet vi støtter. En proxy-wrapper-komponent tar hver enhet som en prop, og parser verdien som props til barnekomponenten.
TypeScript-delen fungerer, som jeg har demonstrert i en TypeScript Playground, men implementeringen får stadig denne feilen:
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 (deler av funksjonaliteten til proxy er fjernet, for å isolere problemet så mye som mulig)
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;
}
Svar #1
Det er ikke en feil i koden din eller i JSX (som foreslått i kommentarene). Dette er en begrensning i JSX i TypeScript.
Med JSX, all koden din som
<Proxy Mobile={{ text: "Mobile" }}>
<ChildElement text="Default" />
</Proxy>
vil bli kompiliert til
React.createElement(Proxy, {
Mobile: {
text: "Mobile"
}
}, React.createElement(ChildElement, {
text: "Default"
}));
Og dermed vil alle barn bli sendt som JSX.Element til foreldreakomponenten. Dette grensesnittet er en svart boks, og ingen egenskaper som prop-typene kan hentes:
JSX-resultattypen
Som standard blir resultatet av et JSX-uttrykk typen any. Du kan tilpasse typen ved å spesifisere JSX.Element-grensesnittet. Det er imidlertid ikke mulig å hente typeinformasjon om elementet, attributtene eller barna til JSX fra dette grensesnittet. Det er en svart boks.
Det er for øyeblikket et åpent problem for dette, hvor en veikart for hvordan JSX.Element kan generaliseres for å støtte ditt brukstilfelle er beskrevet, men uten noen tydelig tidslinje for når og i hvilken grad eller om det vil bli implementert.
Det finnes imidlertid en rask løsning for fortsatt å ha funksjonaliteten du ønsker, ved spesifikt å erklære den generiske typen for din proxy slik: Merk at dette ikke vil utføre typekontroll, for å sikre at de to grensesnittene på Proxy og på barna samsvarer. Dette er ikke mulig (som beskrevet ovenfor)
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>
);
}
og så i din index.tsx slik:
<Proxy<{ text: string }> Mobile={{ text: "Mobile" }}>
<ChildElement text="Default" />
</Proxy>
selvfølgelig kan du også trekke ut prop-typen din { text: string } til et annet grensesnitt for gjenbruk.
