React 19 ist erschienen: Von Server Functions bis zu optimistischen Updates
Das Release ebnet den Weg fĂĽr Fullstack-Anwendungen, bringt aber auch viele Neuerungen fĂĽr die Entwicklung von Single-Page-Anwendungen.
(Bild: iX / erstellt mit ChatGPT)
- Nils Hartmann
Einem bösen Spruch zufolge haben manche JavaScript-Frameworks eine kürzere Haltbarkeit als ein Liter frische Milch. Auf React trifft das aus mehreren Gründen nicht zu. Zum einen feierte die Bibliothek im vergangenen Jahr bereits ihren zehnjährigen Geburtstag und gehört damit zum "alten Eisen" in der Web-Welt. Auch bei der Release-Frequenz übt sich React in Zurückhaltung: Das letzte Major Release erschien bereits im Juni 2022. Seitdem gab es nur wenige Bugfix-Releases.
Das hat sich erst am 5. Dezember 2024 geändert, als nach einer mehrmonatigen Release-Candidate-Phase die finale Version von React 19 veröffentlicht wurde. Diese enthält nicht nur neue Funktionen und Hooks, sondern auch ganz neue Konzepte, die React den Weg in die Fullstack-Entwicklung ebnen sollen.
UnterstĂĽtzung fĂĽr Metadaten
In die Kategorie der kleinen, aber nĂĽtzliche Neuerungen in React 19 fallen die neuen Funktionen zum Arbeiten mit den Metadaten einer Webseite. Metadaten sind DOM-Elemente wie title, meta oder link im head-Bereich des DOM. Deshalb sind sie normalerweise nicht Bestandteil der React-Komponentenhierarchie, lassen sich also mit React-Bordmitteln nicht ohne Weiteres einsetzen. Stattdessen kommen dafĂĽr in vielen Projekten Bibliotheken zum Einsatz.
Mit React 19 stehen diese Elemente nun auch in JSX zur VerfĂĽgung und sind somit in jeder Komponente verwendbar. React sorgt dafĂĽr, dass die Elemente zur Laufzeit an die korrekte Stelle im head-Element wandern. Befinden sich in einer Komponentenhierarchie mehrere solcher Elemente, "gewinnt" das Element, das am weitesten unten in der Komponentenhierarchie steht.
Das nachfolgende Listing illustriert dieses Verhalten anhand des title-Elements. Die App-Komponente setzt den Titel des Browserfensters auf "Blog Application". Sofern die Unterkomponente BlogPost ebenfalls gerendert wird, ĂĽberschreibt die Unterkomponente den Titel, sodass dieser nun dem Titel des dargestellten Blogposts entspricht.
function App({postId}) {
return <main>
<title>Blog Application</title>
{!!postId && <BlogPost postId={postId} />}
</main>
}
function BlogPost({postId}) {
const post = useBlogQuery(postId);
return <article>
<title>{post.title}</title>
{/* ... */}
</article>
}
Neben Titel- und anderen Metadaten lassen sich nun auch script-Elemente und externe Stylesheet-Dateien direkt in einer Komponente rendern. Auch hier sorgt React dafür, dass die entsprechenden script- beziehungsweise link-Elemente am Anfang des DOMs platziert werden und stellt sicher, dass eine Komponente erst dann gerendert wird, wenn der Browser die entsprechenden Ressourcen geladen hat. Die Arbeit mit dynamisch hinzugefügten Scripts (zum Beispiel von externen Anbietern wie der Google-Maps-API) oder Stylesheets wird damit deutlich einfacher. Vervollständigt werden die Metadaten-Features durch die Möglichkeit, Ressourcen vorab zu laden. Dazu können Entwicklerinnen und Entwickler im head-Element des DOM Ressourcen wie Stylesheets oder Schriftarten angeben, die der Browser vorab im Hintergrund herunterlädt, um bei Bedarf schon lokal über sie zu verfügen. Mit React 19 ist es möglich, solche Ressourcen im Code anzugeben. React kümmert sich dann darum, die entsprechenden DOM-Elemente zu erzeugen.
Eine besondere Herausforderung in React-Anwendungen ist das Optimieren beziehungsweise Einsparen von Renderzyklen. React rendert grundsätzlich eine Komponente neu, sobald sich ihr Zustand geändert hat, inklusive ihrer Unterkomponenten. Ein feingranulareres Tracking von Änderungen, wie beispielsweise mit Signals in Angular, gibt es in React nicht. Das muss nicht unbedingt ein Problem sein, denn das Rendern ist in der Regel sehr schnell, auch weil die Änderungen nur im virtuellen DOM ablaufen und React potenziell teure Änderungen am DOM auf das absolute Minimum reduziert. Aber bei tiefen Komponentenhierarchien oder komplexen Komponenten kann es zu Performance-Problemen führen. Deshalb gibt es die memo-Funktion, die eine fertige Komponente cachen kann, sowie die Hooks useMemo und useCallback, um Daten beziehungsweise Funktionen innerhalb einer Komponente zu cachen.
Diese drei Funktionen zu nutzen ist ist allerdings fehleranfällig, weil der Mechanismus, mit dem React prüft, ob die gecachten Objekte noch gültig sind oder neu erzeugt werden müssen, mit Referenzvergleichen arbeitet. Sobald eine Referenz (versehentlich) zu häufig neu erzeugt wurde, ist der Cache de facto abgeschaltet. Außerdem sorgen gerade useMemo- und useCallback-Aufrufe in Komponentenfunktionen schnell für unübersichtlichen Code.
Diese Probleme will der neue React-Compiler verringern. Durch ihn sollen React-Entwickler an vielen Stellen auf memo, useMemo und useCallback verzichten können. Der Compiler analysiert den geschriebenen Code und generiert als Ausgabe dann optimierten Code, der sich ähnlich verhält wie heutiger Code, der mit handgeschriebenen memo-, useMemo- und useCallback-Aufrufen arbeitet und dadurch feingranulare Updates sicherstellt.
Der React Compiler ist ein eigenständiges Projekt und wird unabhängig vom React-19-Release erscheinen.
Vereinfachung von Referenzen und Context
Kleine Vereinfachungen haben auch bestehende APIs erfahren. So ist das ref-Attribut zu einer normalen Property geworden, die sich – wie alle anderen Properties auch – in den Properties einer Komponente deklarieren und verwenden lässt. Die etwas umständliche forwardRef-Funktion wird dadurch überflüssig und ist vom React-Team als veraltet (deprecated) gekennzeichnet. Auch das Erzeugen und Anbieten eines Context hat sich etwas vereinfacht. Die createContext-Funktion liefert künftig direkt die Provider-Komponente zurück. Die Aufteilung in Consumer- und Provider-Komponente entfällt, da die Consumer-Komponente durch die Einführung des Hooks useContext schon seit Längerem nicht mehr notwendig ist.
Unabhängig davon, wie der Context erzeugt wird, gibt es mit der use-Funktion eine neue Möglichkeit, innerhalb einer Komponente darauf – oder auf ein Promise – zuzugreifen. Im Unterschied zu useContext ist die use-Funktion kein Hook (folglich ist sie auch unter API und nicht unter Hooks in der Dokumentation beschrieben) und unterliegt damit auch nicht den "Rules of Hook". Demnach darf eine Hook-Funktion beispielsweise nicht innerhalb eines if-Statements aufgerufen werden. Die use-Funktion ist aber wie jede andere gewöhnliche Funktion verwendbar.
Das nächste Listing zeigt dieses Verhalten. Der CounterContext in der Komponente ist nur dann notwendig, wenn die Checkbox angeklickt ist, da die Komponente andernfalls dessen Daten nicht verwendet. Mit useContext müsste der Context aber immer abgefragt werden, unabhängig vom Zustand der Checkbox. Mit use fragt die Komponente den Context nur dann ab, wenn die Checkbox entsprechend angeklickt ist. Das hat nicht nur Auswirkungen auf den Code, sondern auch auf die Laufzeit: Sofern die Checkbox nicht angeklickt ist und der Context folglich nicht mit use abgefragt wird, rendert React die Komponente auch nicht neu, wenn sich der Wert des Contexts ändert. Bei der Verwendung mit useContext erfolgt auch dann ein erneutes Rendern der Komponente, wenn der Wert des Contexts eigentlich nicht notwendig ist (nicht angeklickte Checkbox).
export const CounterContext = createContext(null);
function CounterContextProvider{children}) {
const value = { /* ... */ }
// bisher:
return <CounterContext.Provider value={value}>{children}</CounterContext.Provider>
// React 19:
return <CounterContext value={value}>{children}</CounterContext>
}
function CounterDisplay() {
const [selected, setSelected] = useState(false);
const counter = selected ? use(CounterContext) : null;
return (
<section>
<label>
Wert anzeigen
<input
type="checkbox"
checked={selected}
onChange={e => setSelected(e.target.checked)}
/>
</label>
{counter && <p>Aktueller Wert: {counter.value}</p>}
</section>
);
}
Videos by heise
Fließende Übergänge: Transitionen
Eine Reihe von neuen Möglichkeiten gibt es bei der Arbeit mit asynchronen Aktionen, wie dem Lesen und Schreiben von serverseitigen Daten. Bereits React 18 führte Transitions ein. Damit lassen sich Aktualisierungen der Oberfläche priorisieren. Normalerweise führt das Ändern eines Zustands dazu, dass React die Komponente unmittelbar neu rendert und das aktualisierte User Interface (UI) im Browser sofort sichtbar ist.
Mit einer Transition können Developer eine Callback-Funktion angeben. Darin lässt sich der Zustand der Komponente ändern. Allerdings wird die Komponente nun im Hintergrund gerendert. Das bestehende UI verändert sich dabei zunächst nicht. Erst nachdem das Rendern im Hintergrund abgeschlossen wurde, aktualisiert sich das UI.
Dieses Verhalten ist besonders bei Updates sinnvoll, die sehr lange dauern und sonst das UI blockieren wĂĽrden. Mit einer Transition erfolgen Neuberechnungen im Hintergrund und das "alte" UI bleibt so lange bestehen, bis das Rendern im Hintergrund erfolgreich war. Damit eine Komponente beispielsweise einen Hinweis auf die laufende Aktualisierung anzeigen kann, gibt eine Transition Auskunft darĂĽber, ob sie gerade ausgefĂĽhrt wird.
Das Listing zeigt dieses Verhalten in der React-18-Variante an einem sehr einfachen Beispiel:
function Counter() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1);
});
};
return <section>
<button onClick={handleClick}>Hochzählen</button>
{isPending && <p>Darstellung wird aktualisiert.</p>}
<CounterView count={count} />
</section>
}
Hier soll CounterView eine komplexe Unterkomponente sein und nach dem Ändern des count-Zustands einige Zeit für das Rendern benötigen. Durch die Transition, in der die State-Änderung erfolgt, geschieht das Rendern im Hintergrund. React rendert nun unmittelbar die Counter-Komponente neu, belässt dabei aber den count-Zustand zunächst in seinem aktuellen Wert. Allerdings liefert der useTransition-Aufruf nun true für isPending zurück. Die Komponente kann also unmittelbar die Information "Darstellung wird aktualisiert" anzeigen. Im Hintergrund wird die Komponente nun neu gerendert. Dort ist count auf den neuen Wert gesetzt und isPending ist false. Die Komponente kann also die künftige Darstellung rendern. Sobald das Rendern abgeschlossen ist, tauscht React die alte Darstellung gegen die neue aus. Tritt beim Rendern im Hintergrund ein Fehler auf, verwirft React die neue Darstellung ersatzlos.
Neu in React 19 ist, dass eine als Transition-Callback verwendete Funktion asynchron sein darf. Solche Funktionen werden als Actions bezeichnet. Damit lassen sich zum Beispiel Serveraufrufe in eine Transition auslagern. Dann wird die vorherige Darstellung so lange beibehalten, bis der asynchrone Serveraufruf abgeschlossen ist. Wie im vorherigen Beispiel liefert React aber die Information, ob die Transition läuft, sodass etwa eine Wartemeldung erscheinen kann.
Das Listing zeigt eine Transition mit asynchroner Action anhand einer Komponente, die einen Button mit einem Like-Counter darstellt:
async function registerLikeOnServer() {
// Server-Call z. B. mit fetch ausfĂĽhren
// neue Likes vom Server zurĂĽckliefern...
}
function LikeButton({initialLikes}) {
const [likes, setLikes] = useState(initialLikes);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(async () => {
const newLikes = await registerLikeOnServer();
setLikes(newLikes);
});
};
return <button onClick={handleClick}
disable={isPending}>{likes} Likes</button>
}
Ein Klick auf den Button löst das Ausführen ein Serveraufrufs aus, der auf dem Server die Anzahl der Likes erhöht und die neue Anzahl zurückliefert (der Code dazu ist React-unabhängig und der Einfachheit halber im Listing ausgelassen). Nach dem Klicken auf den Button rendert React auch hier die Komponente unmittelbar mit dem alten State neu, liefert für isPending aber true zurück. Somit kann der Button während des Speicherns den disabled-Status anzeigen. Im Hintergrund läuft der Server-Call aber bereits. Sobald dieser abgeschlossen ist, wird der Zustand neu gesetzt und dann die Komponente im neuen Zustand gerendert und dargestellt.
(Bild:Â WD Ashari/Shutterstock.com)
Die enterJS 2025 findet am 7. und 8. Mai in Mannheim statt. Die Konferenz bietet einen umfassenden Blick auf die JavaScript-gestĂĽtzte Enterprise-Welt. Der Fokus liegt nicht nur auf den Programmiersprachen JavaScript und TypeScript selbst, sondern auch auf Frameworks und Tools, Accessibility, Praxisberichten, UI/UX und Security.
Das Programm geht voraussichtlich Mitte Januar live. Bis dahin lassen sich vergĂĽnstigte Blind-Bird-Tickets im Online-Shop erwerben.