Wann wird JSX.Element vs ReactNode vs ReactElement verwendet?

142

Ich migriere derzeit eine React-Anwendung zu TypeScript. Bisher funktioniert das ziemlich gut, aber ich habe ein Problem mit den Rückgabetypen meiner renderFunktionen bzw. meiner Funktionskomponenten.

Bisher hatte ich immer JSX.Elementals Rückgabetyp verwendet, jetzt funktioniert dies nicht mehr, wenn eine Komponente beschließt, nichts zu rendern, dh zurückzugeben null, da dies nullkein gültiger Wert für ist JSX.Element. Dies war der Beginn meiner Reise, denn jetzt habe ich im Internet gesucht und festgestellt, dass Sie ReactNodestattdessen verwenden sollten, was auch nulleinige andere Dinge beinhaltet, die passieren können. Dies schien die bessere Wahl zu sein.

Beim Erstellen einer Funktionskomponente beschwert sich TypeScript jedoch über den ReactNodeTyp. Nach einigem Suchen stellte ich wieder fest, dass Sie ReactElementstattdessen für Funktionskomponenten verwenden sollten . Wenn ich dies tue, ist das Kompatibilitätsproblem jedoch behoben, aber jetzt beschwert sich TypeScript erneut darüber, dass es sich nullnicht um einen gültigen Wert handelt.

Um es kurz zu machen, ich habe drei Fragen:

  1. Was ist der Unterschied zwischen JSX.Element, ReactNodeund ReactElement?
  2. Warum geben die renderMethoden von Klassenkomponenten zurück ReactNode, aber Funktionskomponenten ReactElement?
  3. Wie löse ich das in Bezug auf null?
Golo Roden
quelle
Ich bin überrascht, dass dies eintreten würde, da Sie normalerweise den Rückgabetyp nicht mit Komponenten angeben müssen. Welche Typensignatur machen Sie für die Komponenten? Sollte ungefähr so ​​aussehen wie class Example extends Component<ExampleProps> {für Klassen und const Example: FunctionComponent<ExampleProps> = (props) => {für Funktionskomponenten (wo ExamplePropsist eine Schnittstelle für die erwarteten Requisiten). Und dann haben diese Typen genügend Informationen, um auf den Rückgabetyp schließen zu können.
Nicholas Tower
Die Typen sind hier
Jonas Wilms
1
@NicholasTower Unsere Flusenregeln erzwingen die explizite Angabe des Rückgabetyps, und deshalb wird dies angezeigt (was meiner Meinung nach eine gute Sache ist, weil Sie viel mehr darüber nachdenken, was Sie tun, was zum Verständnis beiträgt, als wenn Sie den Compiler einfach alles ableiten lassen). .
Golo Roden
Fair genug, ich habe nicht an Flusenregeln gedacht.
Nicholas Tower
@ JonasWilms Danke für den Link, aber ich glaube nicht, dass dies meine Fragen beantwortet.
Golo Roden

Antworten:

154

Was ist der Unterschied zwischen JSX.Element, ReactNode und ReactElement?

Ein ReactElement ist ein Objekt mit einem Typ und Requisiten.

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

Ein ReactNode ist ein ReactElement, ein ReactFragment, eine Zeichenfolge, eine Zahl oder ein Array von ReactNodes oder null oder undefiniert oder ein Boolescher Wert:

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 ist ein ReactElement, wobei der generische Typ für Requisiten und Typ beliebig ist. Es existiert, da verschiedene Bibliotheken JSX auf ihre eigene Weise implementieren können. Daher ist JSX ein globaler Namespace, der dann von der Bibliothek festgelegt wird. React legt es folgendermaßen fest:

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

Zum Beispiel:

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

Warum geben die Rendermethoden von Klassenkomponenten ReactNode zurück, Funktionskomponenten jedoch ReactElement?

In der Tat geben sie verschiedene Dinge zurück. Components Rückkehr:

 render(): ReactNode;

Und Funktionen sind "zustandslose Komponenten":

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

Dies ist eigentlich aus historischen Gründen .

Wie löse ich das in Bezug auf Null?

Geben Sie es ReactElement | nullgenauso ein wie reagieren. Oder lassen Sie Typescript auf den Typ schließen.

Quelle für die Typen

Jonas Wilms
quelle
Danke für die ausführliche Antwort! Dies beantwortet Frage 1 perfekt, lässt jedoch die Fragen 2 und 3 unbeantwortet. Können Sie bitte auch eine Anleitung dazu geben?
Golo Roden
@goloRoden sicher, ich bin gerade ein bisschen müde und es dauert einige Zeit, um durch die Typen auf einem Handy zu scrollen ...;)
Jonas Wilms
Hallo @JonasWilms. In Bezug auf "Sie tun es nicht. ReactComponent ist definiert als: render (): JSX.Element | null | false;", wo sehen Sie das? Es sieht so aus, als würde es ReactNode an mich zurückgeben ( github.com/DefinitelyTyped/DefinitelyTyped/blob/… ). Auch kleiner Tippfehler, denke ich, ReactComponentsollte React.Componentoder sein Component.
Mark Doliner
@ MarkDoliner Seltsam, ich könnte schwören, dass ich diesen Typ aus der Datei kopiert habe ... Wie auch immer, Sie haben völlig Recht, ich werde bearbeiten
Jonas Wilms
Leute, gibt es irgendwo im Web eine offizielle Dokumentation des React && Typoskripts?
Goran_Ilic_Ilke
25

1.) Was ist der Unterschied zwischen JSX.Element, ReactNode und ReactElement?

ReactElement und JSX.Element sind das Ergebnis eines React.createElementdirekten Aufrufs oder einer JSX-Transpilation. Es ist eine Aufgabe mit type, propsund key. JSX.Elementist ReactElement, wessen propsund typeTyp haben any, also sind sie mehr oder weniger gleich.

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

ReactNode wird als Rückgabetyp für render()Komponenten in Klassen verwendet. Dies ist auch der Standardtyp für childrenAttribute mit PropsWithChildren.

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

In den React-Typdeklarationen sieht es komplizierter aus , entspricht aber:

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

Sie können fast alles zuweisen ReactNode. Normalerweise würde ich stärkere Typen bevorzugen, aber es könnte einige gültige Fälle geben, um sie zu verwenden.


2.) Warum geben die Rendermethoden von Klassenkomponenten ReactNode zurück, Funktionskomponenten jedoch ReactElement?

tl; dr: Es handelt sich um eine aktuelle Inkompatibilität vom Typ TS, die nicht mit der Kernreaktion zusammenhängt .

  • TS-Klassenkomponente: Rückgabe ReactNodemit render(), zulässiger als React / JS

  • TS-Funktionskomponente: Rückgabe JSX.Element | null, restriktiver als React / JS

Im Prinzip unterstützenrender() Komponenten der React / JS-Klasse dieselben Rückgabetypen wie eine Funktionskomponente. In Bezug auf TS sind die verschiedenen Typen eine Typinkonsistenz, die aus historischen Gründen und der Notwendigkeit der Abwärtskompatibilität immer noch beibehalten wird.

Im Idealfall würde ein gültiger Rückgabetyp wahrscheinlich eher so aussehen:

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

3.) Wie löse ich das in Bezug auf Null?

Einige Optionen:
// 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>;

Hinweis: Durch das Vermeiden React.FC werden Sie nicht vor der JSX.Element | nullEinschränkung des Rückgabetyps bewahrt.

Erstellen Reagieren App vor kurzem fielReact.FC von seiner Vorlage, wie es einige Macken wie eine implizite hat {children?: ReactNode}Typdefinition. Eine React.FCsparsame Verwendung ist daher möglicherweise vorzuziehen.

In Randfällen können Sie als Problemumgehung eine Typzusicherung oder Fragmente hinzufügen:
const MyCompFragment: FunctionComponent = () => <>"Hello"</>
const MyCompCast: FunctionComponent = () => "Hello" as any 
// alternative to `as any`: `as unknown as JSX.Element | null`
ford04
quelle