web-dev-qa-db-fra.com

Quand utiliser JSX.Element vs ReactNode vs ReactElement?

Je migre actuellement une application React vers TypeScript. Jusqu'à présent, cela fonctionne plutôt bien, mais j'ai un problème avec les types de retour de mes fonctions render respectivement mes composants de fonction.

Jusqu'à présent, j'avais toujours utilisé JSX.Element comme type de retour, maintenant cela ne fonctionne plus si un composant décide de pas rendre quoi que ce soit, c'est-à-dire retourner null, puisque null n'est pas une valeur valide pour JSX.Element. Ce fut le début de mon voyage, car maintenant j'ai cherché sur le Web et j'ai trouvé que vous devriez utiliser ReactNode à la place, ce qui inclut également null et aussi quelques autres choses qui peuvent arriver. Cela semblait être le meilleur pari.

Cependant, maintenant lors de la création d'un composant fonction, TypeScript se plaint du type ReactNode. Encore une fois, après quelques recherches, j'ai trouvé que pour les composants de fonction, vous devriez utiliser ReactElement à la place. Cependant, si je le fais, le problème de compatibilité a disparu, mais maintenant TypeScript se plaint à nouveau que null n'est pas une valeur valide.

Donc, pour faire court, j'ai trois questions:

  1. Quelle est la différence entre JSX.Element, ReactNode et ReactElement?
  2. Pourquoi les méthodes render des composants de classe renvoient ReactNode, mais les composants de fonction renvoient ReactElement?
  3. Comment résoudre ce problème en ce qui concerne null?
47
Golo Roden

Quelle est la différence entre JSX.Element, ReactNode et ReactElement?

Un ReactElement est un objet avec un type et des accessoires.

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

Un ReactNode est un ReactElement, un ReactFragment, une chaîne, un nombre ou un tableau de ReactNodes, ou null, ou non défini, ou un booléen:

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.Element est un ReactElement, le type générique pour props et type étant any. Il existe, car diverses bibliothèques peuvent implémenter JSX à leur manière, donc JSX est un espace de noms global qui est ensuite défini par la bibliothèque, React le définit comme ceci:

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

Par exemple:

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

Pourquoi les méthodes de rendu des composants de classe renvoient ReactNode, mais les composants de fonction renvoient ReactElement?

En effet, ils retournent des choses différentes. Components retour:

 render(): ReactNode;

Et les fonctions sont des "composants sans état":

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

Comme vous pouvez le voir, les composants fonctionnels peuvent avoir ReactNodes comme enfants, mais ils doivent renvoyer un ReactElement ou null. Si vous y pensez, c'est en fait la même chose qu'avec les composants: ils sont eux-mêmes ReactElements, mais ils peuvent avoir ReactNodes comme des enfants.

Comment résoudre ce problème par rapport à null?

Tapez-le comme ReactElement | null tout comme réagit. Ou laissez TypeScript déduire le type.

source pour les types

42
Jonas Wilms

1.) Quelle est la différence entre JSX.Element, ReactNode et ReactElement?

ReactElement et JSX.Element sont le résultat de l'invocation React.createElement directement ou via la transpilation JSX. C'est un objet avec type, props et key. JSX.Element est ReactElement, dont props et type ont le type any, ils sont donc plus ou moins le même.

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

ReactNode est utilisé comme type de retour pour render() dans les composants de classe. C'est également le type par défaut pour l'attribut children avec PropsWithChildren .

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

Cela semble plus compliqué dans les déclarations de type React , mais est équivalent à:

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...)

Vous pouvez affecter presque tout à ReactNode, cela ne vous aidera pas avec les types forts !


2.) Pourquoi les méthodes de rendu des composants de classe renvoient ReactNode, mais les composants de fonction renvoient ReactElement?

tl; dr: Il s'agit d'une incompatibilité de type TS actuelle non liée au core React, voir le suivi problème principal .

Avec le côté pur React/JS, render() dans les composants de classe supporte les mêmes types de retour qu'un composant de fonction, il y a pas de différences . En ce qui concerne TS, les différents types ne sont actuellement qu'une incohérence de type toujours conservée pour des raisons historiques et le besoin de rétrocompatibilité.

Le problème mentionné veut également prendre en compte le changement qu'un composant rend le type de retour - fonction et classe - entre-temps prend en charge plus de types que JSX.Element. Idéalement, un type de retour valide ressemblerait davantage à ceci:

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

Mais actuellement dans TS, un composant de classe est fixé pour renvoyer ReactNode avec render(), qui est plus permissif que React permet. Le React.FunctionComponent le type de retour est JSX.Element | null, dont les types sont plus restrictifs que React prend en charge.


3.) Comment résoudre ce problème par rapport à null?

Quelques options et orientation pour les types de retour de composant de fonction:

  • Omettre complètement l'annotation de type de retour et s'appuyer sur l'inférence de type

    const MyComp = () => condition ? <div>Hello</div> : null // returns: JSX.Element| null
    
  • Gardez les types render explicites aussi étroits que possible (bonne pratique pour children également)
  • Presque tout peut être assigné à ReactNode, donc je voudrais éviter ça complètement
  • Préférer ReactElement (importation de module "react") À JSX.Element ( portée globale)
  • Utilisez ReactChild au lieu de ReactElement, si vous devez retourner string | number
  • Dans un cas Edge avec un composant fonction, vous pouvez soit lancer soit utiliser des fragments:
    const MyCompFragment: FunctionComponent = () => <>"huhu"</>
    const MyCompCast: FunctionComponent = () => "huhu" as any 
    // or: as unknown as JSX.Element | null
    
  • Pensez à omettre React.FunctionComponent Et utilisez simplement des fonctions simples - le type vientavecbeaucoupcaprices . C'est aussi pourquoi créer React App abandonné à partir de son modèle
3
ford04