TypeScript y React: ¿tipo infantil?

137

Tengo un componente funcional muy simple de la siguiente manera:

import * as React from 'react';

export interface AuxProps  { 
    children: React.ReactNode
 }


const aux = (props: AuxProps) => props.children;

export default aux;

Y otro componente:

import * as React from "react";

export interface LayoutProps  { 
   children: React.ReactNode
}

const layout = (props: LayoutProps) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {props.children}
        </main>
    <Aux/>
);

export default layout;

Sigo recibiendo el siguiente error:

[ts] El tipo de elemento JSX 'ReactNode' no es una función de constructor para elementos JSX. El tipo 'indefinido' no se puede asignar al tipo 'ElementClass'. [2605]

¿Cómo escribo esto correctamente?

Asool
fuente

Respuestas:

132

Sólo children: React.ReactNode

Ridd
fuente
¡Esta es la respuesta correcta aquí! JSX.Elementno es lo suficientemente bueno ya que un niño React válido podría ser una cadena, un booleano, nulo ... también ReactChildestá incompleto por las mismas razones
Pierre Ferry
2
@PierreFerry El problema es que se puede asignar a casi cualquier cosaReactNode . No ayuda en términos de seguridad de tipos, similar a escribir childrencomo any: vea mi respuesta para obtener una explicación.
Ford04
Gracias por tu respuesta @ ford04. En mi caso de uso, quiero que los niños sean de tipo libre. Mi componente acepta cualquier tipo de hijos React válidos. Así que creo que esta sigue siendo la respuesta que estaba buscando :)
Pierre Ferry
47

Para usarlo <Aux>en su JSX, debe ser una función que regrese ReactElement<any> | null. Esa es la definición de un componente de función. .

Sin embargo, actualmente se define como una función que devuelve React.ReactNode, que es un tipo mucho más amplio. Como dicen los mecanografiados de React:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

Asegúrese de que los tipos no deseados se neutralicen envolviendo el valor devuelto en React Fragment ( <></>):

const aux: React.FC<AuxProps> = props =>
  <>{props.children}</>;
Karol Majewski
fuente
No entiendo por qué es necesario envolverlo childrenen un fragmento de reacción. ¿Le importaria explicar? En React es perfectamente válido solo return children;.
sanfilippopablo
1
No si childrenson una matriz. Si ese es el caso, debe envolverlos en un solo nodo o usar React Fragments.
Karol Majewski
Sí, en mi código tengo:, return React.Children.count(children) > 1 ? <>{children}</> : childrenpero mecanografiado se queja de eso. Lo reemplacé con solo <>{children}</>.
sanfilippopablo
2
Se queja porque childrenes una lista de elementos que contiene solo 1 elemento. Creo que funcionaría si lo return React.Children.count(children) > 1 ? <>{children}</> : children[0]hicieras @sanfilippopablo
tamj0rd2
Esto no parece funcionar en las versiones más recientes de React. Inicié sesión en la consola y puedo confirmar que tengo un accesorio para niños, pero incluso con los <React.Fragment>alrededores {props.children}no devuelvo nada.
Mark
34

Esto es lo que funcionó para mí:

interface Props {
  children: JSX.Element[] | JSX.Element
}

Editar , recomendaría usar en su children: React.ReactNodelugar ahora.

Sunknudsen
fuente
8
Esa no es una definición lo suficientemente amplia. El niño podría ser una cuerda.
Mike S
Al menos este ejemplo funcionó para mí, ya que se suponía que mi componente esperaba 1 o más JSX.Elements. ¡Gracias!
Italo Borges
1
Esto no funcionará con expresiones de JavaScript como elementos secundarios <MyComponent>{ condition ? <span>test</span> : null}</MyComponent>. El uso children: ReactNodefuncionará.
Nickofthyme
10

puedes declarar tu componente así:

const MyComponent: React.FunctionComponent = (props) => {
    return props.children;
}
jsina
fuente
9

Puede utilizar ReactChildreny ReactChild:

import React, { ReactChildren, ReactChild } from 'react';

interface AuxProps {
  children: ReactChild | ReactChildren;
}

const Aux = ({ children }: AuxProps) => (<div>{children}</div>);

export default Aux;
Wilk
fuente
6

El tipo de retorno del componente de función está limitado a JSXElement | nullTypeScript. Esta es una limitación de tipo actual, React puro permite más tipos de retorno.

Fragmento de demostración mínimo

Puede usar una aserción de tipo o Fragmentos como solución alternativa:

const Aux = (props: AuxProps) => <>props.children</>; 
const Aux2 = (props: AuxProps) => props.children as ReactElement; 

ReactNode

children: React.ReactNode puede ser subóptimo, si el objetivo es tener tipos fuertes para Aux .

Casi todo se puede asignar al ReactNodetipo actual , que es equivalente a {} | undefined | null. Un tipo más seguro para su caso podría ser:

interface AuxProps {
  children: ReactElement | ReactElement[]
}

Ejemplo:

Dadas las Auxnecesidades de los elementos de React como children, accidentalmente le agregamos un string. Entonces la solución anterior sería un error en contraste conReactNode : eche un vistazo a los patios de recreo vinculados.

Typed childrentambién es útil para accesorios que no son JSX, como una devolución de llamada de Render Prop .

ford04
fuente
5

También puede utilizar React.PropsWithChildren<P>.

type ComponentWithChildProps = React.PropsWithChildren<{example?: string}>;
Sibren
fuente
3

La forma general de encontrar cualquier tipo es mediante un ejemplo. La belleza del mecanografiado es que tiene acceso a todos los tipos, siempre que tenga los @types/archivos correctos .

Para responder a esto yo mismo, solo pensé en un componente que usa react que tiene el childrenprop. ¿Lo primero que le vino a la mente? ¿Qué tal un <div />?

Todo lo que necesita hacer es abrir vscode y crear un nuevo .tsxarchivo en un proyecto de reacción con @types/react.

import React from 'react';

export default () => (
  <div children={'test'} />
);

Al pasar el cursor sobre el childrenaccesorio, se muestra el tipo. ¿Y qué sabes? Su tipo es ReactNode(no es necesario ReactNode[]).

ingrese la descripción de la imagen aquí

Luego, si hace clic en la definición de tipo, lo lleva directamente a la definición de interfaz childrenproveniente DOMAttributes.

// node_modules/@types/react/index.d.ts
interface DOMAttributes<T> {
  children?: ReactNode;
  ...
}

Nota: ¡Este proceso debe usarse para encontrar cualquier tipo desconocido! Todos están ahí esperando que los encuentres :)

Nickofthyme
fuente
2

Como tipo que contiene niños, estoy usando:

type ChildrenContainer = Pick<JSX.IntrinsicElements["div"], "children">

Este tipo de contenedor para niños es lo suficientemente genérico para admitir todos los casos diferentes y también está alineado con la API de ReactJS.

Entonces, para su ejemplo, sería algo como:

const layout = ({ children }: ChildrenContainer) => (
    <Aux>
        <div>Toolbar, SideDrawer, Backdrop</div>
        <main>
            {children}
        </main>
    <Aux/>
)
Denis
fuente
1

Estas respuestas parecen estar desactualizadas: React ahora tiene un tipo integrado PropsWithChildren<{}>. Se define de manera similar a algunas de las respuestas correctas en esta página:

type PropsWithChildren<P> = P & { children?: ReactNode };

Tim Iles
fuente
1
¿Qué hay Pen el caso de ese tipo?
sunpietro
Pes el tipo de accesorios de su componente
Tim Iles
-2

Los componentes de React deben tener un solo nodo contenedor o devolver una matriz de nodos.

Su <Aux>...</Aux>componente tiene dos nodos divy main.

Trate de envolver a sus hijos en una divde Auxcomponente.

import * as React from 'react';

export interface AuxProps  { 
  children: React.ReactNode
}

const aux = (props: AuxProps) => (<div>{props.children}</div>);

export default aux;
Dinesh Pandiyan
fuente
1
No es verdad. En React 16+, este ya no es el caso, además el error diría esto si fuera el caso
Andrew Li