Startseite / Artikel

Entfernen des Provider-Wrappers: Rendering-Kontext aus Server-Komponenten

Erfahren Sie, wie React 19.3 es einem Server-Component ermöglicht, einen Client-Context direkt darzustellen, wie Flight diesen serialisiert und welche Einschränkungen bei der Lektüre sowie die geltenden Werte weiterhin bestehen.

Verlag: REACTAPP.TOP7 Min. Lesezeit1602 Wörter

Quellen: React 19.3: Render a Context Directly in a Server Component, No Provider Wrapper

Fast jede Codebasis für React Server Components enthält eine kleine 'use client'-Datei, deren einzige Aufgabe darin besteht, einen Context zu erstellen und ein Provider-Komponente zu exportieren, das eine einzige Eigenschaft weiterleitet. Dieser Wrapper existierte, weil Server Components nicht dazu in der Lage waren, den Context selbst auszugeben. React 19.3 beseitigt diese Einschränkung, sodass der Server nun den Context-Element direkt ausgeben kann und der Wrapper entfernt werden kann.

Dieser Leitfaden erläutert das alte Muster, das neue Muster, was das Flight-Protokoll tatsächlich im Hintergrund tut, sowie die Fallstricke, die darüber entscheiden, ob der Wechsel in Ihrem Framework heute funktioniert. Die Beispiele benötigen eine echte Server Components-Runtime (ein Framework mit RSC-Bundler-Unterstützung und einem Server), daher sollten sie als vollständiger Referenzcode betrachtet werden und nicht einfach in eine Sandbox kopiert werden. Für Hintergrundinformationen dazu, wie die Server- und Client-Komponenten einer RSC-Anwendung zusammenarbeiten, sehen Sie unsere Übersicht zu der Architektur hinter React Server Components.

Das alte Muster: Eine Komponente, die nur einen Prop weiterleitet

Server-Komponenten können createContext nicht aufrufen, da ein Context eine Client-Seiten-Statusmechanik darstellt. Dennoch benötigt der unter ihnen liegende Client-Baum regelmäßig Daten, die der Server bereits besitzt – wie den angemeldeten Benutzer, die Feature-Flags eines Tenants oder die Spracheinstellungen der Anfrage. Der einzige Weg, diese Daten zu übertragen, waren die Props einer Client-Komponente, welche anschließend den Provider darstellte.

Das Client-Modul beginnt mit der üblichen Direktive und dem Import.

'use client';
import { createContext } from 'react';

Daraufhin wird der Context erstellt und ein Provider exportiert, dessen Körper nichts anderes tut, als currentUser als value weiterzugeben.

export const UserContext = createContext(null);// exists only to forward one prop
export function UserProvider({ currentUser, children }) {
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

Die Server-Komponentenseite

Das Layout, eine Server-Komponente, importiert den Wrapper anstelle des Contexts.

import { UserProvider } from './user-context';

Es holt den Benutzer auf dem Server ab und gibt ihn als Prop an den Wrapper weiter.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserProvider currentUser={currentUser}>
      {children}
    </UserProvider>
  );
}

Falls man versucht hat, den Wrapper in React 19.2 zu überspringen, tritt ein verwirrender Laufzeitfehler auf, der mit „Element type is invalid“ beginnt und anschließend darauf hinweist, dass ein lazy Elementtyp als Context statt als Klasse oder Funktion gelöst wurde. Es sieht wie ein Problem des Bundlers aus, doch die Ursache liegt im Renderer. Ein Context, der über die Client-Grenze importiert wird, erreicht den Server-Renderer als clientseitige Referenz, die verspätet gelöst wird – 19.2 konnte diese Referenzen nur in Komponenten umwandeln. Ein Context-Objekt war niemals ein zulässiges Ergebnis.

Das neue Muster: Den Context selbst rendern

In 19.3 reduziert sich das Client-Modul auf die Direktive, den Import sowie einen einzigen Export.

'use client';
import { createContext } from 'react';

Es gibt keine Komponente mehr, nur noch das Context-Objekt.

export const UserContext = createContext(null);

Sein Renderen aus dem Layout

Das Layout importiert nun direkt UserContext.

import { UserContext } from './user-context';

Die JSX hat dieselbe Struktur wie zuvor, mit dem Unterschied, dass das Element der Context ist und die Eigenschaft value heißt.

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

Nutzung am Client

Für die Nutzer ändert sich nichts. Ein Client-Component, das unterhalb des Layouts liegt, importiert denselben Context und liest ihn mit use aus.

'use client';
import { use } from 'react';
import { UserContext } from './user-context';

Hier holt das Avatar-Menü den Benutzer aus dem Context und rendernt das Bild.

export function AvatarMenu() {
  const user = use(UserContext);
  return <img src={user.avatar} alt={user.name} />;
}

Achten Sie auf die Syntax von React 19 im Layout: Es wird <UserContext value> rendernt anstelle von <UserContext.Provider value>. Die .Provider-Form funktioniert weiterhin am Client, doch der Server rendernt das Context-Objekt selbst – daher sollte man in Server-Code die kürzere Form verwenden.

Was Flight kann und was diese Funktion nicht kann

Wenn ein Server-Komponente <UserContext value={currentUser}> rendernt, läuft auf dem Server überhaupt keine Context-Logik ab. Der Flight-Serializer gibt ein Element aus, das als Client-Referenz typisiert ist und auf das Modul user-context.js sowie dessen Export UserContext verweist. Dieses Element enthält als Props { value: currentUser, children }. Diese Props werden genauso serialisiert wie alle anderen Props, die zum Client übertragen werden. Sobald der Browser dieses Modul lädt, wird die Client-Referenz zum eigentlichen Context-Objekt, und React erstellt einen Provider, der den übertragenen Wert speichert. Die in facebook/react #35675 dokumentierte Renderer-Verbesserung brachte es mit sich, dass der lazy-type-Codepfad neben Funktionen auch Context-Objekte, memo und forwardRef akzeptieren kann.

Diese Konzeption hat zwei Folgen.

Der Wert muss serialisierbar sein

Der Context-Wert unterliegt denselben Regeln wie jede Eigenschaft, die zum Client übertragen wird. Zulässige Typen umfassen Primitive wie Zeichenketten und Zahlen, einfache Objekte und Arrays, Instanzen von Date, Map und Set, Promises, JSX-Elemente sowie Server Functions. Gewöhnliche Funktionen, Klasseninstanzen und alles mit einem benutzerdefinierten Prototype gehören nicht dazu. Ein einfaches currentUser-Objekt funktioniert; eine Instanz von UserService mit Methoden hingegen nicht. Die Dokumentation zu 'use client' listet die vollständigen Regeln auf.

Es handelt sich nicht um einen Server-side Context

In dem Pull Request wird ausdrücklich darauf hingewiesen, dass man weiterhin nicht use(Context) innerhalb einer Server Component aufrufen kann. Server Components haben keine Möglichkeit, einen Context zu lesen, da es auf dem Server keinen Provider gibt, den sie zur Lektüre nutzen könnten. Die Funktion dient lediglich dazu, einen Wert auf einem kürzeren Weg an den Client-Tree zu übergeben.

Warum es sich lohnt, den Wrapper zu entfernen

Jeder geteilte Wert kostete früher eine Datei und ein Komponente. Das scheint unbedeutend, bis man sie zählt: Eine App mit Kontexten für den Benutzer, Theme, Lokalisation, Feature-Flags, Tenant und Analytics enthält sechs nahezu identische Wrapper – und jeder davon birgt die Gefahr, ein Attribut zu übersehen oder einen Standardwert falsch einzugeben. Mit der neuen Struktur befindet sich der Anbieter direkt dort, wo die Daten abgerufen werden, und das mentale Modell „welche Teile laufen auf dem Server und welche auf dem Client“ verliert dadurch einen Sonderfall.

Die Reaktion der Community auf die Änderung bestand größtenteils aus Erleichterung statt Begeisterung. Entwickler merkten an, es sei seltsam gewesen, dass dies ursprünglich nicht unterstützt wurde, da ein Context-Wert im Grunde nur Daten sind, die im Speicher gespeichert werden. Diese Intuition ist richtig und erklärt, warum die Korrektur in React selbst eher gering ausfiel.

Achtungspunkte vor der Migration

  • Ihr Framework muss die 19.3 Flight-Pakete enthalten. Die Änderung befindet sich in react-server-dom-webpack, -turbopack und -parcel sowie im Reconciler – nicht nur in react. Überprüfen Sie die von Ihrem Framework festgelegte React-Version; eine nicht übereinstimmende Version führt zum gleichen „Element type is invalid“-Fehler wie zuvor beschrieben.
  • Definieren Sie den Context in einer mit 'use client' markierten Datei. Ein im Server-Code definierter Context kann auf diese Weise nicht dargestellt werden, da das gesamte Verfahren davon abhängt, dass das Objekt im Client-Bundle vorhanden ist.
  • Das Lesen auf dem Server bleibt weiterhin unmöglich. Ein Server-Component, der den Wert benötigt, sollte ihn entweder als Argument erhalten oder selbst abrufen; use(Context) führt in diesem Fall zu einem Fehler.
  • Nur serialisierbare Werte funktionieren. Wenn der alte Wert eine Instanz einer Klasse war oder Methoden enthielt, verbarg der Wrapper diese, wodurch das Problem nun als Serialisierungsfehler auftaucht.
  • Eine Promise ist ein zulässiger Wert. Da Promises über Grenzen hinweg serialisiert werden, führt das Schreiben von <UserContext value={getCurrentUser()}> ohne await zum Streamen des Wertes, wodurch die Verarbeiter ihn zweimal mit use(use(UserContext)) entpacken müssen. Das kann eine nützliche Streaming-Technik sein, doch es ist auch leicht, einen Fehler zu machen, indem man das await vergisst.
  • Standardwerte bleiben eine Sache des Clients. Der Standardwert, der an createContext(null) übergeben wird, gilt dort, wo kein Anbieter über einem Verarbeiter steht, und die Darstellung des Anbieters auf dem Server ändert das nicht.
  • Vorerst beim Wrapper bleiben

    Falls Ihr Framework noch nicht auf Version 19.3 aktualisiert wurde, behalten Sie den Wrapper bei. Das UserProvider-Muster funktioniert in jeder React 19-Version sowie in jedem Framework. Bei der Aktualisierung ist die Migration einfach:

    • Löschen Sie die Wrapper-Funktion aus dem Client-Modul.
    • Importieren Sie den Context in der Server-Komponente anstelle des Wrappers.
    • Ändern Sie den Namen der weitergeleiteten Eigenschaft in value.

    Sie können bei der Umstellung Ihres Frameworks auf 19.3 Datei für Datei migrieren. Es ist nicht notwendig, alles auf einmal umzusetzen – sobald beide Formen funktionieren, gibt es keinen Unterschied in der Laufzeit.

    Hauptpunkte

    • In React 19.3 kann eine Server-Komponente einen aus einem 'use client'-Modul exportierten Context rendern, wodurch einzigzweckige Provider-Wrapper überflüssig werden.
  • Im Hintergrund sendet Flight eine Client-Referenz zusammen mit serialisierten Eigenschaften; auf dem Server läuft kein Context-Behavior.
  • Der Wert muss den normalen Serialisierungsregeln entsprechen, und Server Components können weiterhin keinen Context lesen.
  • Stellen Sie sicher, dass Ihr Framework die entsprechenden Flight-Pakete mitverpackt, bevor Sie Wrapper löschen, und achten Sie auf versehentliche Promise-Werte.
  • Zum Detaillesen lesen Sie die React 19.3-Release-Nachrichten auf react.dev sowie die Referenz zu Server Components.