784 votes

Quand utiliser JSX.Element vs ReactNode vs ReactElement ?

Je suis actuellement en train de migrer 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 mon fichier 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 no rendre n'importe quoi, c'est-à-dire retourner null puisque null n'est pas une valeur valide pour JSX.Element . C'était le début de mon voyage, parce que maintenant j'ai cherché sur le web et j'ai trouvé que vous devriez utiliser ReactNode à la place, qui comprend également null et aussi quelques autres choses qui peuvent arriver. Cela semblait être le meilleur choix.

Cependant, maintenant, lors de la création d'un composant de fonction, TypeScript se plaint de l'absence d'une fonction. ReactNode type. Encore une fois, après quelques recherches, j'ai trouvé que pour les composants de fonction, il faut utiliser ReactElement à la place. Cependant, si je fais cela, le problème de compatibilité disparaît, mais TypeScript se plaint à nouveau de ce qui suit null n'étant pas une valeur valide.

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

  1. Quelle est la différence entre JSX.Element , ReactNode y ReactElement ?
  2. Pourquoi les render les méthodes des composants de la classe retournent ReactNode mais les composants de la fonction reviennent ReactElement ?
  3. Comment puis-je résoudre ce problème par rapport à null ?

776voto

Jonas Wilms Points 52419

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

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

 type Key = string | number

 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 undefined, ou 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.Element est un ReactElement, avec le type générique pour les props et le type étant quelconque. Il existe, car diverses bibliothèques peuvent implémenter JSX de leur propre manière, donc JSX est un espace de nom 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-elles ReactNode, mais les composants de fonction renvoient ReactElement ?

En effet, ils renvoient des choses différentes. Component Le retour de l'UE :

 render(): ReactNode;

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

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

Ceci est en fait dû à raisons historiques .

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

Tapez-le comme ReactElement | null comme le fait la réaction. Ou laissez Typescript déduire le type.

<a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts" rel="noreferrer">source pour les types</a>

98voto

ford04 Points 112

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

ReactElement et JSX.Element sont le résultat de l'invocation de React.createElement directement ou via la transpilation JSX. Il s'agit d'un objet avec type , props y key . JSX.Element es ReactElement dont props y type avoir le type any Ils sont donc plus ou moins les mêmes.

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 la classe. C'est également le type par défaut pour children avec l'attribut PropsWithChildren .

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

Cela semble plus compliqué dans le Déclarations de type React mais c'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 attribuer presque tout à ReactNode . Je préfère généralement des types plus forts, mais il peut y avoir des cas valables pour l'utiliser.


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

tl;dr : Il s'agit d'une incompatibilité de type TS actuelle non lié au noyau de React .

  • Composant de la classe TS : retours ReactNode con render() plus permissif que React/JS

  • Composante de la fonction TS : retours JSX.Element | null plus restrictif que React/JS

En principe, render() dans les composants de classe React/JS supporte les mêmes types de retour en tant que composante de la fonction. En ce qui concerne les TS, les différents types sont une incohérence de type encore conservée pour des raisons historiques et le besoin de rétrocompatibilité.

Idéalement, un type de retour valide ressemblerait probablement plus à ça :

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

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

Quelques options :

// 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>;

Note : Éviter le site React.FC ne sauvera pas vous de la JSX.Element | null restriction du type de retour.

Créer récemment une application React abandonné React.FC à partir de son modèle, car il présente certaines particularités, comme l'utilisation implicite d'un {children?: ReactNode} définition du type. Ainsi, en utilisant React.FC avec parcimonie pourrait être préférable.

Dans les cas limites, vous pouvez ajouter une assertion de type ou des fragments comme solution de rechange :

const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X