Startseite / Artikel / React 19-Upgrade-Auswahl: Welche neuen APIs ersetzen die Workarounds, die Sie haben

React 19-Upgrade-Auswahl: Welche neuen APIs ersetzen die Workarounds, die Sie haben

Ein praktischer Überblick über die Verwendung, Server Actions, useOptimistic und den React Compiler – inklusive der wichtigen Einschränkungen sowie einem Plan dafür, welche Änderungen in React 19 zuerst umgesetzt werden sollten.

1579 Wörter

React 19 fügte mehr API-Funktionen hinzu als jede andere Version in den letzten Jahren, und die Überflutung an Dokumentationen erschwert es, herauszufinden, was für eine bestehende Codebasis wichtig ist. Für die meisten Teams ersetzen einige der neuen Funktionen bereits vorhandene Workarounds, eine ändert die Art und Weise, wie man mit dem Laden von Daten umgeht, während der Rest warten kann. Im Folgenden sind die wichtigsten Funktionen aufgelistet, zusammen mit Code-Beispielen vor und nach der Anwendung sowie Hinweisen, die leicht übersehen werden können, damit Sie den Upgrade nach dem Nutzen planen können.

use: Lesen von Promises und Context während der Renderung

Die use-API ist die bedeutendste Neuerung und verhält sich anders als alle anderen Hooks, die Sie kennen. Reguläre Hooks müssen auf der obersten Ebene eines Components in derselben Reihenfolge bei jeder Renderung aufgerufen werden. use ist von dieser Regel ausgenommen: Sie können es nach einem vorzeitigen Return, innerhalb einer Bedingung oder in einer Schleife aufrufen.

In dem untenstehenden Beispiel gibt die Komponente bei Fehlen eines userId eine Gästevorlage zurück und liest den Benutzer erst danach ein:

// React 19 — use() can be called inside conditionals and loops
function UserProfile({ userId }) {
  if (!userId) return <GuestView />;

  // This is valid in React 19
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
}

Die eigentliche Stärke liegt darin, was use entgegennimmt: ein Promise oder ein Context. Wird ein Promise übergeben, pausiert React die Komponente, bis dieses abgeschlossen ist. Der folgende Vergleich zeigt, wie viel wegfällt: Die ältere Version verfolgt die Daten sowie ein Ladeflagge im State, ruft in einem Effect ab und rendernt manuell einen Spinner; die Version von React 19 liest den Wert direkt ein:

// Before React 19
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// React 19
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

Die Arbeit wurde lediglich verschoben, anstatt zu verschwinden: Die nächstgelegene <Suspense>-Grenze zeigt die Ersatzlösung, solange das Promise aussteht, und die nächstgelegene Fehlergrenze kümmert sich um Ablehnungen. Die Komponente beschreibt nur den Erfolgsfall.

Das Promise muss stabil sein

use erwartet denselben Promise bei jeder Neuzeichnung. Wenn fetchUser(userId) bei jeder Neuzeichnung einen neuen Promise erstellt, suspendiert React jedes Mal erneut, was zu wiederholten Anfragen oder einem Komponenten führt, der niemals abgeschlossen wird; React warnt in Client Components vor nicht gekachelten Promises, die während der Neuzeichnung erstellt werden. Behandeln Sie die Beispiele daher lediglich als Veranschaulichungen der Struktur. Der Promise sollte von etwas stammen, das ihn kachtet: einer Datenbibliothek, die Suspense berücksichtigt, einem Framework-Loader oder einer Server Component, die die Anfrage startet und den Promise als Prop weiterleitet. Es wird manchmal vorgeschlagen, den Aufruf in useMemo zu packen, doch React garantiert nicht, dass gememorierte Werte beibehalten werden, weshalb dies keine zuverlässige Kachelung darstellt.

Server Actions als React-Funktionality

Nutzer des Next.js App Router kennen Server Actions bereits. In React 19 sind sie Teil von React selbst (in den Dokumentationen werden sie nun Server Functions genannt) und stehen in jedem Framework zur Verfügung, das Server Components unterstützt.

In dem Beispiel wird eine asynchrone Funktion definiert, die mit 'use server' markiert ist; sie fügt einen Benutzer hinzu, aktualisiert die Liste erneut und gibt sie an das action-Attribut eines Forms weiter:

// Server Action — runs on the server, called from the client
async function submitForm(formData) {
  'use server';

  const name = formData.get('name');
  await db.users.create({ name });
  revalidatePath('/users');
}

// Client component
function UserForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Add User</button>
    </form>
  );
}

Das action-Attribut von <form> akzeptiert nun eine Funktion, auch eine asynchrone; React führt sie bei Absenden in einem Übergang aus und übermittelt ihr die FormData. Der Ausführungszustand, optimistische Aktualisierungen und Fehler stammen von begleitenden APIs: useFormStatus oder useActionState für den Ausführungszustand und das Ergebnis, useOptimistic für sofortige Rückmeldungen sowie Error Boundaries bei Fehlern. Sie schreiben die Mutation; React koordiniert den Rest.

In einer echten Anwendung muss ein Detail im Auszug angepasst werden. Eine Funktion mit der inline-Direktive 'use server' kann nur in einem Server Component definiert werden. Wenn UserForm ein Client Component ist, verschieben Sie submitForm in eine eigene Datei, fügen Sie oben 'use server' hinzu und importieren Sie diese Datei.

Während Server Components die statische Benutzeroberfläche aus dem Client-Bundle entfernen, beseitigen Server Actions die manuell geschriebenen API-Wege für Mutationen: weniger Code, weniger Kommunikationsaustausche. Behandeln Sie jede Aktion dennoch als öffentlichen Endpunkt und überprüfen Sie dort die Eingaben sowie die Autorisierung.

useOptimistic: sofortige Rückmeldung ohne doppelten Zustand

Früher bedeutete das Rendern eines Ergebnisses vor der Serverbestätigung, dass man manuell zusätzlichen Zustand verwalten musste. useOptimistic nimmt den aktuellen Zustand sowie eine update-funktion ähnliche Funktion und gibt den zum Rendern geeigneten Zustand zusammen mit einer Funktion zurück, die eine optimistische Änderung durchführt. Beim Hinzufügen eines To-Do-Eintrags wird ein temporärer Eintrag eingefügt, der als ausstehend markiert ist und daher leicht verblasst dargestellt wird, bis die Speicherung abgeschlossen ist:

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos, newTodo) => [...currentTodos, newTodo]
  );

  async function handleAdd(text) {
    addOptimisticTodo({ id: 'temp', text, pending: true });
    await saveTodo(text); // Server Action
  }

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.7 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Wenn man addOptimisticTodo aufruft, zeigt React die optimistische Liste sofort an. Wenn die zugehörige Aktion abgeschlossen ist, wird der optimistische Wert verworfen und React rendernt erneut aus dem todos-Prop, der zu diesem Zeitpunkt bereits den gespeicherten Eintrag enthalten sollte.

Zuvor speicherten Sie bestätigte und ausstehende Kopien der Daten und räumten manuell auf, wenn Abweichungen auftraten; nun übernimmt der Hook diese Lebenszyklusverwaltung. Zwei Bedingungen sind wichtig. Erstens muss die optimistische Aktualisierung innerhalb einer Aktion oder eines Übergangs stattfinden, beispielsweise in einer Funktion, die an das action-Attribut eines Forms übergeben wird oder in startTransition eingebettet ist; ein Aufruf aus einem einfachen Ereignishandler führt dazu, dass React warnt, und die Aktualisierung wird möglicherweise nicht angezeigt. Zweitens muss der Elternteil nach dem Speichern tatsächlich aktualisierte todos-Daten erhalten, in der Regel durch Neuvollständigung, andernfalls verschwindet die neue Eintragung, wenn der optimistische Zustand zurückgesetzt wird. Temporäre IDs wie 'temp' führen ebenfalls zu Konflikten, wenn zwei Einträge schnell hinzugefügt werden, daher sollte ein eindeutiger ID generiert werden. Diese Randfälle behandeln wir in fünf Ausfallmodi von useOptimistic Rollback.

Der React Compiler: Memoisierung standardmäßig

Der React Compiler, früher React Forget, wurde zusammen mit React 19 als optionale Kompiliereschritt eingeführt und hat den nachhaltigsten Einfluss auf den täglichen Code. Er fügt zur Kompilierzeit Memoisierung ein, sodass Werte und Callbacks wiederverwendet werden, wenn sich die Eingaben nicht ändern, und Kinderkomponenten keine erneute Darstellung benötigen, wenn die Props identisch sind. Manuelle Verwendung von useMemo, useCallback und React.memo wird meist überflüssig, wie der Vergleich zeigt:

// Before: manual memoization required
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
const stableCallback = useCallback(() => doSomething(id), [id]);
const MemoizedChild = React.memo(ChildComponent);

// After React Compiler: write normal code
const expensiveValue = compute(a, b);
const handleClick = () => doSomething(id);
// ChildComponent renders only when its props change - automatically

Man muss weiterhin verstehen, wann und warum Komponenten neu rendern, doch die manuellen Ausweichmöglichkeiten sind weitaus weniger wichtig. Unsere Übersicht zu Mustern, die unnötige Neurenderungen auslösen bleibt weiterhin eine nützliche Grundlage.

Für neue Projekte ist der Compiler eine solide Standardwahl. In vorhandenem Code sollte er vorsichtig eingeführt werden: Er geht davon aus, dass die Komponenten rein sind und den Regeln von React folgen, sodass Code, der während des Renderns verändert wird, nach der Kompilierung anders funktionieren kann. Aktivieren Sie ihn schrittweise, führen Sie Ihre Tests durch und nutzen Sie zunächst den ESLint-Plugin, um Verstöße aufzudecken. Überprüfen Sie die aktuelle React-Dokumentation bezüglich des Veröffentlichungszustands und der unterstützten React-Versionen.

Was anzuwenden ist und in welcher Reihenfolge

Sofortiger Nutzen, geringes Risiko

  • useOptimistic dort, wo Formulare mit Server-Zuständen interagieren.
  • Server Actions, wenn Sie Next.js 14 oder neuer verwenden und API-Routen für Änderungen ersetzen möchten.

Was sich lohnt, im Voraus zu planen

  • use, sobald Ihre Datenschicht stabile, gecachte Promises bereitstellt; es passt gut zu Suspense.
  • Den React Compiler in neuen Projekten einsetzen oder zunächst in gut getesteten Teilen einer bestehenden Anwendung.
  • Nicht dringend für die meisten Anwendungen

    • Änderungen bei ref: ref kann nun als normales Prop an Funktionskomponenten übergeben werden, sodass forwardRef nicht mehr benötigt wird.
    • Verbesserungen im Context-System, die hauptsächlich Änderungen an der Entwicklererfahrung darstellen und kein neues Verhalten einführen.
    • Unterstützung für Dokumentmetadaten, die es Komponenten ermöglichen, <title> und <meta>-Tags anzuzeigen, die React in den Dokumenten-Header einfügt.

    Hauptpunkte

    • React 19 ist eher eine Weiterentwicklung als eine grundlegende Überarbeitung. Seine neuen APIs formalisieren Muster, die Produktionsteams bereits manuell anwenden: optimistische Aktualisierungen, Server-Mutations und bedingte Datenabfrage.
    • use verlagert das Laden und die Fehlerbehandlung auf Suspense sowie Error Boundaries, funktioniert aber nur gut mit Promises, die außerhalb der Render-Logik gekachtet sind.
    • Server Actions und useOptimistic ergänzen sich ideal; die ersten kümmern sich um den Schreibvorgang, die zweiten verbergen ihre Latenz.
    • Voraussetzung ist, dass Ihre Komponenten den Regeln von React folgen, dann macht der Compiler die Memoisierung standardmäßig.

    Die wichtige Frage ist nicht, ob man upgraden soll, sondern welches dieser Konzepte eine Workaround-Lösung ersetzt, die man derzeit verwendet. Beginnen Sie dort.

    Zusätzliche Literatur

  • Asynchrone Formulare in React 19 mit use(), useActionState und useOptimistic — Wie use(), useActionState, useFormStatus und useOptimistic die manuelle Laden- und Fehlerverwaltung in React 19 ersetzen sowie welche Fallstricke jede dieser Hooks heimlich verbergen.