javascript - Hvordan bruke React.FC<props>-typen når barna enten kan være en React-node eller en funksjon
Jeg har denne prøvekomponenten
import React, { FC, ReactNode, useMemo } from "react";
import PropTypes from "prop-types";
type Props = {
children: ((x: number) => ReactNode) | ReactNode;
};
const Comp: FC<Props> = function Comp(props) {
const val = useMemo(() => {
return 1;
}, []);
return (
<div>
{typeof props.children === "function"
? props.children(val)
: props.children}
</div>
);
};
Comp.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};
export default Comp;
Min hensikt her er at children-egenskapen til komponenten kan enten være
en
node, som beskrives somAlt som kan gjengis: tall, strenger, elementer eller et array (eller fragment) som inneholder disse typene.
en
funksjon(eller en «render-prop») som rett og slett henter en verdi fra komponenten og returnerer en annennode
Poenget her er å være eksplisitt, at children enten kan være den ene (node, som er omtrent alt) eller den andre (som helt enkelt er en function)
Problemet
Jeg står overfor følgende problemer med typetesten.
hvis jeg lar koden stå som presentert her, får jeg følgende feilmelding på linjen
? props.children(val)Dette uttrykket kan ikke kalles. Ikke alle bestanddeler av typen 'Function | ((x: number) => ReactNode) | (string & {}) | (number & {}) | (false & {}) | (true & {}) | ({} & string) | ({} & number) | ({} & false) | ({} & true) | (((x: number) => ReactNode) & string)
Jeg forstår ikke denne feilen.
- hvis jeg endrer
Props-typen til å være
type Props = {
children: (x: number) => ReactNode;
};
og stole på Reacts egen type PropsWithChildren<P>=P & { children?: ReactNode }; for å håndtere tilfeller der children ikke er en funksjon, så får jeg feilen
(egenskap) children?: PropTypes.Validator<(x: number) => React.ReactNode> Type 'Validator' kan ikke tildeles typen 'Validator<(x: number) => ReactNode>'. Type 'ReactNodeLike' kan ikke tildeles typen '(x: number) => ReactNode'. Type 'string' kan ikke tildeles typen '(x: number) => ReactNode'.ts(2322) Comp.tsx(5, 3): Den forventede typen kommer fra egenskapen 'children' som er erklært her på typen
på linjen children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
Den eneste løsningen er å la Props-typen være
type Props = {
children: (x: number) => ReactNode;
};
og også endre Comp.propTypes til å være children: PropTypes.func.isRequired, som ikke er det jeg ønsker, siden jeg vil være eksplisitt.
Spørsmålet
Hvordan kan jeg holde koden eksplisitt, som vist i starten av dette spørsmålet, og samtidig unngå at typetesting gir meg feil?
CodeSandbox-lenke
Svar #1
kort fortalt :
Typen React.FC er årsaken til feilen ovenfor:
- Det inkluderer allerede standard
childrentypet somReactNode, som blir slått sammen (&) med din egenchildren-type som er inneholdt iProps. ReactNodeer en ganske bred type som begrenser kompilatorens evne til å avgrensechildren-unionstypen til en kallbar funksjon i kombinasjon med punkt 1.
En løsning er å utelate FC og bruke en mer spesifikk type enn ReactNode for å dra nytte av typesikkerhet:
type Renderable = number | string | ReactElement | Renderable[]
type Props = {
children: ((x: number) => Renderable) | Renderable;
};
Mer detaljer
Først av alt, her er de innebygde React-typene:
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean
| null | undefined;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
type PropsWithChildren<P> = P & { children?: ReactNode };1.) Du bruker FC<Props> for å typisere Comp. FC inkluderer internt allerede en children-deklarasjon typet som ReactNode, som blir slått sammen med children-definisjonen fra Props:
type Props = { children: ((x: number) => ReactNode) | ReactNode } &
{ children?: ReactNode }
// this is how the actual/effective props rather look like
2.) Når man ser på ReactNode-typen, vil man se at typene blir betydelig mer komplekse. ReactNode inkluderer typen {} via ReactFragment, som er supertypen til alt unntatt null og undefined. Jeg kjenner ikke de eksakte beslutningene bak denne typestrukturen, microsoft/TypeScript#21699 antyder historiske og bakoverkompatibilitetsårsaker.
Som en konsekvens er children-typene bredere enn tiltenkt. Dette forårsaker dine opprinnelige feil: typevakt typeof props.children === "function" kan ikke lenger innsnevre typen "muddle" riktig til function.
Løsninger
Utelat React.FC
Til slutt er React.FC bare en funksjonstype med ekstra egenskaper som propTypes, displayName osv., med en meningsfull, bred children-type. Å utelate FC her vil resultere i tryggere, mer forståelige typer for kompilator og IDE-visning. Hvis jeg tar definisjonen din Noe som kan rendres for children, kunne det være:
import React, { ReactChild } from "react";
// You could keep `ReactNode`, though we can do better with more narrow types
type Renderable = ReactChild | Renderable[]
type Props = {
children: ((x: number) => Renderable) | Renderable;
};
const Comp = (props: Props) => {...} // leave out `FC` type
Egendefinert FC-type uten children
Du kan definere din egen FC-versjon, som inneholder alt fra React.FC bortsett fra de brede children-typene:
type FC_NoChildren<P = {}> = { [K in keyof FC<P>]: FC<P>[K] } & // propTypes etc.
{ (props: P, context?: any): ReactElement | null } // changed call signature
const Comp: FC_NoChildren<Props> = props => ...
Lekeplassprøve
Svar #2
Jeg tror at global fagforening kan hjelpe:
type Props = {
children: ((x: number) => ReactNode);
} | {
children: ReactNode;
};
