React 19: Alle Neuerungen im Überblick

React 19: Erster Eindruck

Avatar von Raffael Thermann

React hat sich als das beliebteste und meistgenutzte Werkzeug in der Frontend-Entwicklung etabliert, was sich sowohl in der Anzahl der Installationen über npm als auch in der Häufigkeit der gestellten Fragen auf Stack Overflow widerspiegelt.

Nun stehen bei der populären UI Library mit Version 19 signifikante Veränderungen und Neuerungen an.

Ein offizielles Veröffentlichungsdatum wurde vom Core-Entwicklerteam noch nicht festgelegt. Es wird jedoch erwartet, dass React 19 noch im ersten Halbjahr 2024 veröffentlicht wird. Wahrscheinlich wird die Veröffentlichung im Zusammenhang mit der React Conf 2024 stattfinden, die aktuell (vom 15. bis 16. Mai 2024) in Nevada stattfindet.

Bei Instagram ist React 19 bereits seit Ende 2023 erfolgreich in der Produktivumgebung im Einsatz. Doch was genau hat sich nun in Version 19 von React geändert oder ist sogar ganz neu hinzugekommen?

React Compiler

Die vermutlich weitreichendste Neuerung dürfte die Einführung eines Compilers sein. Dieser wandelt den React Code in “plain vanilla” JavaScript-Code um und führt dabei im Hintergrund eine beträchtliche Performance-Optimierung durch.

Dank der vom Compiler durchgeführten Optimierungen heißt es ab sofort auch: No more wasted renders!

Die Nutzung des Compilers führt nämlich auch dazu, dass die bei vielen React-Entwicklern unbeliebte Aufgabe der “Memoization” zukünftig komplett entfällt.

Das bedeutet im Klartext: Die Verwendung der Funktion memo(), sowie der Hooks useMemo() und useCallback() ist zukünftig nicht mehr nötig.

Exkurs Memoization
Bei der Memoization handelt es sich um eine Optimierungstechnik, bei der Ergebnisse von Pure Functions nach erstmaligem Aufruf im Cache gespeichert werden. Bei Pure Functions handelt es sich um Funktionen, die bei gleichen Argumenten stets die gleichen Ergebnisse zurückgeben.

Wird die Funktion mit den selben Parametern erneut aufgerufen, wird das gecachte Ergebnis zurück gegeben, anstatt die Funktion erneut auszuführen. Das ist im Ergebnis erheblich perfomanter.

In React werden
Komponenten mittels memo(),
Objekte mittels useMemo() und
Funktionen mittels useCallback()
memoiziert.

Des Weiteren ist die manuelle Verwendung von React.lazy() nicht mehr nötig, da der React Compiler zukünftig ebenfalls die Optimierung für lazy loading übernimmt.

use() Hook

Die use() Hook ist ein brandneuer React Hook, der es erlaubt, Promises oder Context asynchron zu laden. Somit kann er alternativ zu den Hooks useEffect() und useContext() verwendet werden.

Beispiel 1: use() statt useEffect() für Promises am Beispiel eines GET requests

An unserem ersten Beispiel lässt sich erkennen, dass wir unseren Promise mit deutlich weniger Code realisieren können, da unter anderem das Error-Handling effizienter gestaltet werden kann.

// React 18 mit useEffect()
import { useEffect, useState } from 'react';

export default function FetchDataWithUseEffect() {
  const [myData, setMyData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    const fetchMyData = async () => {
      try {
        setLoading(true);
        setError("");
        const response = await fetch('https://api...');
        if (!response.ok) {
          throw new Error('Network response was not in order');
        }
        const data = await response.json();
        setMyData(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchMyData();
  }, []);

  return (
    <div>
      {loading && <p>Loading...</p>}
      {!loading && error && <p>Something went wrong: {error}</p>}
      {!loading && myData && (
        <div>{myData.value}</div>
      )}
    </div>
  );
}

// React 19 mit use()
import { use } from 'react';

const fetchData = async () => {
  const res = await fetch('https://api...');
  return res.json();
};

const FetchDataWithUse = () => {
  const myData = use(fetchData());
  return (
    <p>{myData.value}</p>
  );
};

Bemerkenswert ist hier, dass wir komplett auf die Nutzung von useState() verzichten. Um den useState() für myData kümmert sich unser use() Hook.

Um hingegen den useState() für loading und error zu ersetzen, betten wir eleganter Weise unsere Komponente zum Abrufen der Daten in eine Suspense und ErrorBoundary-Komponente ein, so dass wir den loading und error State nicht mehr manuell per setLoading() oder setError() setzen müssen.

import { Suspense } from 'react';
import { ErrorBoundary } from "react-error-boundary";

<ErrorBoundary fallback={<p>Something went wrong</p>}>
  <Suspense fallback={<p>Loading...</p>}>
    <FetchDataWithUse />
  </Suspense>
</ErrorBoundary>

Bislang war der asynchrone Datenabruf mit Verwendung von Suspense nur mittels React Query möglich.

Beispiel 2: use() statt useContext() für Context

Ab React 19 wird im Zusammenhang mit Context lediglich useContext() mit use() ersetzt, die Erstellung des Context mit createContext() und das Einbinden des Providers bleibt identisch.

# React 18 mit useContext()
const user = useContext(UserContext)

# React 19 mit use()
const user = use(UserContext)

Ref wird zu einer normalen Prop: forwardRef() entfällt

Die Schritte zur Erstellung einer ref bleiben auch in React 19 gleich. Zuerst erstellen wir unsere ref mittels useRef() und anschließend übergeben wir diese als prop an unsere Child-Komponente.

Neu ist der Zugriff auf unsere ref. Die Nutzung von forwardRef() entfällt, die ref wird nun als prop weitergereicht.

// # React 18 mit forwardRef()
import { forwardRef } from 'react';

const MyButton = forwardRef((props, ref) => (
  <button ref={ref} {...props} >
    {props.children}
  </button>
));

// # React 19: ref als prop
const MyButton = ({ ref, children, ...props }) => 
  <button ref={ref} {...props}>
      {children}
  </button>;

Die Ausmusterung von forwardRef() dürfte auf großen Anklang stoßen und ist nicht zuletzt auch den Anstrengungen der React Community zu verdanken.

Server Components

Ab Next.js 13.4+ sind Komponenten bereits per default Server Components. In React gibt es ab Version 19 nun die Möglichkeit, per use server Komponenten zu Server Components zu machen.

Bei Server Components gibt es keine re-renders, die Komponente bzw. UI wird einmalig auf dem Server geladen, bevor sie an den Client geschickt wird.

Exkurs Directives
Als Directives bezeichnet man die beiden Strings use client und use server, mit denen in der ersten Zeile einer Komponente angegeben werden kann, ob die Komponente auf dem Client oder auf dem Server gerendered werden soll.

use client gibt die Anweisung an die Komponente, JavaScript auf dem Client zu laden.
use server wird benötigt, wenn der Seiteninhalt bei einem Clientaufruf vom Server gerendert zurückgegeben werden soll.

Server Components bringen einige Vorteile mit sich:

  • Schnellere initiale Seitenaufrufe, sowie generell eine verbesserte Performance, da weniger JavaScript-Code an den Client geschickt und dort geladen werden muss.
  • Datenbankabfragen lassen sich serverseitig grundsätzlich schneller realisieren.
  • SEO-Optimierung, da Server Components leichter gecrawled werden können.
  • Schnelle und einfache serverseitige Ausführung von Datenabnfragen und API-Aufrufen.

Actions mit neuen Hooks: useFormStatus(), useFormState() und useOptimistic()

Actions sind eine Möglichkeit der Handhabung von Forms und können ab React 19 sowohl auf dem Client, als auch auf dem Server laufen.

Mittels der prop action={} kann eine Funktion direkt in ein <form/> HTML Tag gereicht werden. Die Funktion der Action (in unserem Beispiel search) wird dann ausgeführt, sobald das Formular abgesendet wird:

<form action={search}>
  <input name="query" />
  <button type="submit">Search</button>
</form>

Das ist zwar auch über onSubmit={} möglich, aber die Verwendung von Actions hat den Vorteil, dass folgende neue Hooks genutzt werden können:

  • useFormStatus()
  • useFormState()
  • useOptimistic()

useFormStatus()

Wenn die Action asynchron ist, ist ungewiss, wann genau die Übertragung des Formulars abgeschlossen ist. Um zu verhindern, dass das Formular erneut abgesendet wird, kann der neue useFormStatus() Hook verwendet werden. Damit lässt sich in unserem Beispiel der Button zum Absenden der Form deaktivieren.

import { useFormStatus } from "react-dom";

function Submit() {
  const status = useFormStatus();
  return <button disabled={status.pending}>
  {status.pending ? 'Submitting...' : 'Submit'}</button>;
}

Insgesamt bietet der useFormStatus() Hook vier verschiedene Parameter. Bei Bedarf lassen sich diese natürlich auch direkt destrukturieren.

const { pending, data, method, action } = useFormStatus();
useFormState()

Der useFormState() Hook ähnelt dem useState() Hook mit dem Unterschied, dass mittels einer Action-Funktion statt einem Setter der neue State gesetzt wird.

Das erste Argument unseres useFormState() Hook ist die Action, das zweite Argument der Initial Value. Unsere Action selbst ist eine Funktion, die den vorherigen Wert (Previous State) und die Daten der Form (formData) als Parameter enthält.

import { useFormState } from 'react-dom';

function submitFeedback(prevState, formData) {
  sendFeedbackToServer(formData).then(response => {
    return { submitted: true, message: response };
  }).catch(error => {
    return { submitted: false, message: "Failed to submit feedback. Please try again later." };
  });
}

function FeedbackForm() { 
    const [feedbackState, submitFeedbackAction] = useFormState(submitFeedback, null);

    return (
        <form action={submitFeedbackAction}>
            <textarea name="feedback" placeholder="Enter your feedback"></textarea>
            <button type="submit">Submit Feedback</button>
            {feedbackState.submitted && <p>{feedbackState.message}</p>}
        </form>
   

Die in unserem Beispiel fiktive sendFeedbackToServer()-Funktion gibt ein Promise zurück, deren Ergebnis als neuer State von feedbackState gesetzt wird. Der neue State wird nicht mit einem Setter eines useState() Hook gesetzt.

useOptimistic()

Mittels des neu eingeführten useOptimistic() Hook können sogenannte „Optimistic State Updates“ durchgeführt werden. Das sind temporäre Aktualisierungen, die automatisch rückgängig gemacht werden, sobald der endgültige State erreicht ist.

Damit kann zum Beispiel bestimmt werden, was dem User angezeigt wird, während er auf ein Ergebnis wartet; wie zum Beispiel auf die Antwort des Servers.

Die Syntax sieht dabei folgendermaßen aus:

import { useOptimistic } from 'react-dom';

const [optimisticState, addOptimistic] = useOptimistic(
  state,
  (currentState, optimisticValue) => {
  # Hier geben wir den neuen State mit Optimistic Value zurück
  },
);

In der Praxis könnte die useOptimistic() Hook zum Beispiel so einen Einsatz finden:

import { useState } from 'react';
import { useOptimistic } from 'react-dom';

function ChatMessenger() {
  const [messages, setMessages] = useState([]);

  const [optimisticMessages, addOptimisticMessage] = useOptimistic(messages,

    (state, newMessage) => [...state, { text: newMessage, sending: true }]
  );

  async function formAction(formData) {
    const message = formData.get("message");

    // Temporäres Update, während wir auf den Response des Servers warten
    addOptimisticMessage(message);
    const createdMessage = await createMessage(message);

    // Temporäre message wird mit der finalen message ersetzt
    setMessages((messages) => [...messages, { text: createdMessage }]);
  }

  // ...
}

Mittels addOptimisticMessage(message) setzen wir das temporäre Update ( alsoein Optimistic State Update). Sobald wir den Response des Servers erhalten haben, wird der temporäre Client State mit dem finalen Server State ersetzt.

Document Metadata direkt in Komponenten

Ab React 19 können HTML Tags wie <style>, <title>, <meta>, <link> und <script> direkt in einer Komponente gerendert werden.

// Bis React 18 mit react-helmet
import {Helmet} from "react-helmet";

const MyPage = () => {
  return (
      <Helmet>
        <meta charSet="utf-8" />
        <title>My page</title>
      </Helmet>
  );
}


// Ab React 19
const MyPage = () => {
  return (
    <>
      <meta charSet="utf-8" />
      <title>My page</title>
    </>
  );
}

Bislang ging dies nur mithilfe des externen Packages react-helmet. Da react-helmet sein letztes Versionsupdate vor vier Jahren hatte, sollten Projekte mit Migration auf React 19 unbedingt den direkten Einsatz von HTML Metadaten anstelle der Verwendung von react-helmet in Erwägung ziehen.

Assets Loading: preload und preinit

Bei Aufruf einer oder mehrerer Komponenten wird im Browser oftmals zunächst die Ansicht gerendert, gefolgt von Stylesheets, Schriftarten und Bildern.

Das kann dazu führen, dass der Übergang von einer nicht gestylten zu einer gestylten Ansicht flackert.

In React 19 werden Bilder und andere Dateien, während die Benutzer die aktuelle Seite erkunden, asynchron im Hintergrund geladen. Diese Verbesserung soll dazu beitragen, die Ladezeiten von Seiten zu verbessern und Wartezeiten zu verkürzen.

Mittels preload kann eine externe Ressource, wie zum Beispiel ein Skript oder ein CSS-Stylesheet, heruntergeladen werden, ohne dass es nach dem Laden direkt ausgeführt wird.

Das Preloading von externen Ressourcen erfolgt über eine Event-Handler-Funktion, die vor dem Wechsel zu einer neuen Seite oder in einen neuen Zustand aufgerufen wird. Auf diese Weise beginnt der Prozess früher, als wenn das Laden der Ressourcen erst während des Renderings der neuen Seite oder des neuen Zustands starten würde.

In der Praxis könnte preload beispielsweise wie im folgenden Beispiel gezeigt verwendet werden. Damit kann eine Ressource (in unserem Beispiel style.css) bereits geladen werden, bevor der Nutzer zu der jeweiligen Seite navigiert.

import { preload } from 'react-dom';

function App() {
  preload("https://.../style.css", {as: "style"});
  return ...;
}

Mittels preinit hingegen wird die Ressource nach dem Herunterladen sofort ausgeführt.

Wichtig: Soll preinit ein Stylesheet laden, dann ist die Angabe der precedence obligatorisch!

import { preinit } from 'react-dom';

function App() {
  preinit("https://.../style.css", {as: "style", precedence: "high"});
  return ...;
}

Außerdem führt React im Hintergrund ein sogenanntes “Lifecycle Suspense” für das Laden von Ressourcen ein; einschließlich Skripten, Stylesheets und Schriftarten.

Mit dieser Funktion kann React feststellen, wann der Inhalt bereit ist, angezeigt zu werden. Hierdurch wird ein “ungestyltes” Flackern vermieden.

Web Components

Mit Webkomponenten können benutzerdefinierte HTML-Elemente (zum Beispiel <user-profile </user-profile> oder <popup-dialog </popup-dialog>) unter Verwendung von HTML, CSS und JavaScript wie Standard-HTML-Tags erstellt und eingebunden werden.

Bislang war die Integration benutzerdefinierter HTML-Elemente in React nicht so ohne Weiteres möglich, sondern bedurfte der Installation zusätzlicher Packages.

Dies wird zukünftig erheblich vereinfacht. So sollen Web Components ab React 19 ohne weiteres direkt eingefügt werden können.

Fazit

Nachdem wir uns nun die neuen Funktionen und Änderungen angesehen haben, fassen wir nochmal kurz das Wesentliche zusammen.

Die Einführung des React Compilers und die damit einhergehende Lösung der Memoization-Problematik dürfte ein tatsächlicher Game-Changer in der React-Entwicklung sein.

Die mühselige Evaluierung von wasted renders mittels des Profilers der React Dev Tools und das nötige Wissen um den richtigen Einsatz von memo(), useMemo() und useCallback() erforderte in der Vergangenheit viel Erfahrung und gehört bis dato zu den unbeliebtesten Aufgaben vieler React-Entwickler.

Eine weitere große Erleichterung dürfte der use() Hook sein; außerdem sollte die Ausmusterung von forwardRef() auf großen Anklang stoßen.

Die weiteren Neuheiten bringen vor allem Performance-Verbesserungen mit sich. Wobei auch die neuen Hooks für Form Actions sicherlich schnell auf großen Anklang im Einsatz stoßen sollten.

Wer sich bereits jetzt mit den neuen Funktionalitäten vertraut machen möchte, kann die experimentelle Version 19 mittels npm i react@beta react-dom@beta installieren.

Avatar von Raffael Thermann

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.