javascript - Cómo usar el tipo React.FC<props> cuando los children pueden ser un nodo de React o una función
Tengo este componente de ejemplo
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;
Mi intención aquí es que la propiedad children del componente pueda ser
un
nodo, que se describe comoCualquier cosa que se pueda representar: números, cadenas, elementos o un arreglo (o fragmento) que contenga estos tipos.
una
función, (o un "render prop") que simplemente obtiene un valor desde dentro del componente y devuelve otronodo
el punto aquí es ser explícito, que los children pueden ser o uno (node, que es prácticamente todo) o el otro (que es simplemente una function)
El problema
Sin embargo, estoy enfrentando los siguientes problemas con la verificación de tipo.
si dejo el código como se presenta aquí, obtengo el siguiente mensaje de error en la línea
? props.children(val)Esta expresión no se puede invocar. No todos los constituyentes del tipo 'Function | ((x: number) => ReactNode) | (string & {}) | (number & {}) | (false & {}) | (true & {}) | ({} & string) | ({} & number) | ({} & false) | ({} & true) | (((x: number) => ReactNode) & string)'}
No entiendo este error.
- si cambio el tipo
Propspara que sea
type Props = {
children: (x: number) => ReactNode;
};
y confiar en el propio type PropsWithChildren<P>=P & { children?: ReactNode }; de React para manejar el caso donde children no es una función, entonces obtengo el error
(propiedad) children?: PropTypes.Validator<(x: number) => React.ReactNode> El tipo 'Validator' no se puede asignar al tipo 'Validator<(x: number) => ReactNode>'. El tipo 'ReactNodeLike' no se puede asignar al tipo '(x: number) => ReactNode'. El tipo 'string' no se puede asignar al tipo '(x: number) => ReactNode'.ts(2322) Comp.tsx(5, 3): El tipo esperado proviene de la propiedad 'children' que se declara aquí en el tipo
en la línea children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
La única solución es dejar el tipo Props como
type Props = {
children: (x: number) => ReactNode;
};
y también cambia el Comp.propTypes a children: PropTypes.func.isRequired, lo cual no es lo que quiero, ya que quiero ser explícito.
La pregunta
¿Cómo puedo mantener el código explícito, tal como se presentó al inicio de esta pregunta, y también evitar que la verificación de tipos me arroje errores?
Enlace de CodeSandbox
Respuesta #1
resumen:
El tipo React.FC es la causa del error anterior:
- Ya incluye
childrenpor defecto tipados comoReactNode, los cuales se combinan (&) con tu propio tipo dechildrencontenido enProps. ReactNodees un tipo bastante amplio que limita la capacidad del compilador para reducir el tipo de unión dechildrena una función invocable en combinación con el punto 1.
Una solución es omitir FC y usar un tipo más específico que ReactNode para aprovechar la seguridad de tipos:
type Renderable = number | string | ReactElement | Renderable[]
type Props = {
children: ((x: number) => Renderable) | Renderable;
};
Más detalles
Primero que nada, aquí están los tipos integrados de React:
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.) Usas FC<Props> para tipar Comp. FC internamente ya incluye una declaración de children tipada como ReactNode, la cual se fusiona con la definición de children de Props:
type Props = { children: ((x: number) => ReactNode) | ReactNode } &
{ children?: ReactNode }
// this is how the actual/effective props rather look like
2.) Al observar el tipo ReactNode, verás que los tipos se vuelven considerablemente más complejos. ReactNode incluye el tipo {} a través de ReactFragment, que es el supertipo de todo excepto null y undefined. No conozco las decisiones exactas detrás de esta forma de tipo, microsoft/TypeScript#21699 sugiere razones históricas y de compatibilidad hacia atrás.
Como consecuencia, los tipos children son más amplios de lo previsto. Esto provoca tus errores originales: el guardia de tipo typeof props.children === "function" ya no puede restringir correctamente el tipo "muddle" a function.
Soluciones
Omitir React.FC
Al final, React.FC es solo un tipo de función con propiedades adicionales como propTypes, displayName, etc., con un tipo de children amplio y con opinión. Omitir FC aquí resultará en tipos más seguros y comprensibles para el compilador y la visualización en el IDE. Si tomo tu definición Cualquier cosa que pueda ser renderizada para children, eso podría ser:
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
Tipo FC personalizado sin children
Podrías definir tu propia versión de FC, que contenga todo de React.FC excepto esos tipos amplios de children:
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 => ...
Muestra de parque infantil
Respuesta #2
Creo que la unión global podría ayudar:
type Props = {
children: ((x: number) => ReactNode);
} | {
children: ReactNode;
};
