¿Cuándo usar JSX.Element vs ReactNode vs ReactElement?

Resuelto Golo Roden asked hace 5 años • 5 respuestas

Actualmente estoy migrando una aplicación React a TypeScript. Hasta ahora esto funciona bastante bien, pero tengo un problema con los tipos de retorno de mis renderfunciones, específicamente en mis componentes funcionales.

Siempre lo he usado JSX.Elementcomo tipo de retorno, ahora esto ya no funciona si un componente decide no renderizar nada, es decir, devuelve null, ya que nullno es un valor válido para JSX.Element. Este fue el comienzo de mi viaje. Busqué en la web y descubrí que deberías usar ReactNodeen su lugar, que incluye nully algunas otras cosas que pueden suceder.

Sin embargo, al crear un componente funcional, TypeScript se queja del ReactNodetipo. Nuevamente, después de buscar un poco, descubrí que para los componentes funcionales debería usar ReactElementen su lugar. Sin embargo, si lo hago, el problema de compatibilidad desaparece, pero ahora TypeScript vuelve a quejarse de nullno ser un valor válido.

Para abreviar la historia, tengo tres preguntas:

  1. ¿ Cuál es la diferencia entre JSX.Elementy ?ReactNodeReactElement
  2. ¿Por qué regresan los rendermétodos de los componentes de clase ReactNode, pero regresan los componentes funcionales ReactElement?
  3. ¿Cómo soluciono esto con respecto a null?
Golo Roden avatar Sep 27 '19 02:09 Golo Roden
Aceptado

¿Cuál es la diferencia entre JSX.Element, ReactNode y ReactElement?

A ReactElementes un objeto con un tipo y accesorios.

 type Key = string | number

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

A ReactNodees un ReactElement, un ReactFragment, un string, un numbero un arrayde ReactNodes, o null, o undefined, o un boolean:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;

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

JSX.Elementes un ReactElement, siendo el tipo genérico para accesorios y el tipo cualquiera. Existe, ya que varias bibliotecas pueden implementar JSX a su manera, por lo tanto, JSX es un espacio de nombres global que luego la biblioteca configura, React lo configura así:

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}

Por ejemplo:

 <p> // <- ReactElement = JSX.Element
   <Custom> // <- ReactElement = JSX.Element
     {true && "test"} // <- ReactNode
  </Custom>
 </p>

¿Por qué los métodos de renderizado de los componentes de clase devuelven ReactNode, pero los componentes de función devuelven ReactElement?

De hecho, devuelven cosas diferentes. Components regreso:

 render(): ReactNode;

Y las funciones son "componentes sin estado":

 interface StatelessComponent<P = {}> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement | null;
    // ... doesn't matter
}

En realidad, esto se debe a razones históricas .

¿Cómo soluciono esto con respecto a nulo?

Escríbalo tal ReactElement | nullcomo lo hace reaccionar. O deje que Typecript infiera el tipo.

fuente para los tipos

Jonas Wilms avatar Sep 26 '2019 19:09 Jonas Wilms

1.) ¿Cuál es la diferencia entre JSX.Element, ReactNode y ReactElement?

ReactElement y JSX.Element son el resultado de invocar React.createElementdirectamente o mediante transpilación JSX. Es un objeto con type, propsy key. JSX.Elementes ReactElement, cuyo propsy typetienen tipo any, por lo que son más o menos iguales.

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");

ReactNode se utiliza como tipo de retorno para render()componentes de clase. También es el tipo predeterminado para childrenatributos con PropsWithChildren.

const Comp: FunctionComponent = props => <div>{props.children}</div> 
// children?: React.ReactNode

Parece más complicado en las declaraciones de tipo React , pero es equivalente a:

type ReactNode = {} | null | undefined;
// super type `{}` has absorbed *all* other types, which are sub types of `{}`
// so it is a very "broad" type (I don't want to say useless...)

Puedes asignar casi todo a ReactNode. Normalmente preferiría tipos más fuertes, pero puede haber algunos casos válidos para usarlos.


2.) ¿Por qué los métodos de representación de los componentes de clase devuelven ReactNode, pero los componentes de función devuelven ReactElement?

tl;dr: Es una incompatibilidad de tipo TS actual no relacionada con el núcleo de React .

  • Componente de clase TS: regresa ReactNodecon render(), más permisivo que React/JS

  • Componente de función TS: devuelve JSX.Element | null, más restrictivo que React/JS

En principio, render()en React/JS los componentes de clase admiten los mismos tipos de retorno que un componente de función. Con respecto a TS, los diferentes tipos son una inconsistencia de tipos que aún se mantiene debido a razones históricas y a la necesidad de compatibilidad con versiones anteriores.

Idealmente, un tipo de devolución válido probablemente se parecería más a esto:

type ComponentReturnType = ReactElement | Array<ComponentReturnType> | string | number 
  | boolean | null // Note: undefined is invalid

3.) ¿Cómo soluciono esto con respecto a nulo?

Algunas opciones:
// Use type inference; inferred return type is `JSX.Element | null`
const MyComp1 = ({ condition }: { condition: boolean }) =>
    condition ? <div>Hello</div> : null

// Use explicit function return types; Add `null`, if needed
const MyComp2 = (): JSX.Element => <div>Hello</div>; 
const MyComp3 = (): React.ReactElement => <div>Hello</div>;  
// Option 3 is equivalent to 2 + we don't need to use a global (JSX namespace)

// Use built-in `FunctionComponent` or `FC` type
const MyComp4: React.FC<MyProps> = () => <div>Hello</div>;

Nota: Evitar React.FC no lo salvará de la JSX.Element | nullrestricción del tipo de devolución.

La aplicación Create React se eliminóReact.FC recientemente de su plantilla, ya que tiene algunas peculiaridades, como una {children?: ReactNode}definición de tipo implícita. React.FCPor lo tanto , podría ser preferible usarlo con moderación.

En casos extremos, puede agregar una aserción de tipo o Fragmentos como solución alternativa:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
ford04 avatar Jan 21 '2020 11:01 ford04