React 19.3 Jenseits der Schlagzeilen: Submitter, Fullscreen-Props und Bundle-Größe
Eine praktische Übersicht über die kleineren Änderungen in React 19.3: die submitter-Eigenschaft, die Event-Props für Vollbildmodus, parallele Übergänge, onReset, wichtige Fehlerbehebungen sowie die tatsächliche Größenzunahme.
Die Veröffentlichungshinweise konzentrieren sich in der Regel auf einige zentrale Neuerungen, und React 19.3 ist keine Ausnahme. Darunter befindet sich eine Liste mit „weiteren bemerkenswerten Änderungen“ sowie eine lange Seite mit Fehlerbehebungen, von denen die meisten nur aus einer Zeile bestehen und leicht übersprungen werden können. Einige dieser Zeilen verändern jedoch die Art und Weise, wie Sie heute Komponenten schreiben, erklären Fehler, um die Sie möglicherweise bereits herumgekommen sind, oder beeinflussen das, was Sie den Nutzern bereitstellen. Diese Anleitung geht grob in der Reihenfolge darauf ein, wie wahrscheinlich es ist, dass sie für Sie relevant sind, und endet mit einer fundierten Antwort auf die Frage, die jeder nach einem Upgrade stellt: Wie viel größer ist das Paket geworden?
Erfahren, welcher Button ein Formular abgeschickt hat
Ein Formular mit zwei Absenden-Buttons, zum Beispiel Speichern und Speichern & schließen, muss wissen, welchen Button der Benutzer gedrückt hat. Browser beantworten diese Frage bereits seit längerem: Das native SubmitEvent stellt eine submitter-Eigenschaft bereit, die auf den Button verweist, der die Übermittlung ausgelöst hat. Reacts synthetisches Event übertrug diese Eigenschaft jedoch nicht weiter. Die üblichen Workarounds bestanden darin, e.nativeEvent.submitter zu lesen oder – weniger elegant – den zuletzt geklickten Button im State zu speichern und darauf zu hoffen, dass keine Eingabe per Tastatur übersehen wird. React 19.3 fügt submitter zum synthetischen Absenden-Event hinzu (#35590).
Der einfachste Weg, den Unterschied zu erkennen, besteht darin, ein Formular mit beiden Versionen auszuprobieren. Die Demo beginnt mit ihren Imports; der Namespace-Import dient nur dazu, dass das Komponente React.version ausgeben kann.
import { useState } from 'react';
import * as React from 'react';
Die Komponente verhindert die Standardnavigation und schreibt anschließend die React-Version sowie den Wert von e.submitter in den Zustand. Jeder Button besitzt seinen eigenen value, genau wie man es in echtem Code verwenden würde, um unterschiedliche Abläufe zu steuern.
export default function App() {
const [msg, setMsg] = useState('press either button');
return (
<form onSubmit={e => {
e.preventDefault();
setMsg(`${React.version}: e.submitter → ${e.submitter ? e.submitter.value : String(e.submitter)}`);
}}>
<button value="save">Save</button>{' '}
<button value="save-close">Save & close</button>
<p className="msg">{msg}</p>
</form>
);
}
Eine einzige Regel gestaltet die Ausgabelinie in einer Monospaced-Schriftart.
.msg { margin: 10px 0 0; font-family: ui-monospace, monospace; }
Das gleiche Formular in der Version 19.3
Die Version 19.3 der Demo ist identisch – genau das ist der Punkt: Nur die React-Version ändert sich. Die Imports bleiben unverändert.
import { useState } from 'react';
import * as React from 'react';
Auch die Komponente ist identisch.
export default function App() {
const [msg, setMsg] = useState('press either button');
return (
<form onSubmit={e => {
e.preventDefault();
setMsg(`${React.version}: e.submitter → ${e.submitter ? e.submitter.value : String(e.submitter)}`);
}}>
<button value="save">Save</button>{' '}
<button value="save-close">Save & close</button>
<p className="msg">{msg}</p>
</form>
);
}
Ebenso gilt das für die Stylesheet-Datei.
.msg { margin: 10px 0 0; font-family: ui-monospace, monospace; }
Am 19.2 wird undefined angezeigt, weil das synthetische Ereignis kein solches Feld besitzt. Am 19.3 wird je nach Schaltfläche save oder save-close angezeigt. Berücksichtigen Sie, welchen Codepfad dies betrifft. Wenn Sie über Form-Actions statt eines onSubmit-Handlers absenden, hat React bereits beim Erstellen von FormData (#29028) den Absender gelesen, sodass das name- und value-Paar der gedrückten Schaltfläche bereits in den Daten der Aktion enthalten war. Die neue Eigenschaft ist für Ereignishandler wichtig.
Eine praktische Anmerkung: Der value-Wert des Absenders hilft nur, wenn Sie tatsächlich value (und in der Regel auch name) für jede Schaltfläche festlegen. Eine Schaltfläche ohne diese Werte liefert einen leeren String, den man leicht fälschlicherweise für ein fehlendes Feature halten könnte.
Vollbild-Ereignisse als erstklassige Props
Die Fullscreen-API sendet fullscreenchange und fullscreenerror an das betroffene Element. React erkannte diese Ereignisnamen nicht, weshalb Komponenten wie Video-Player manuell mit addEventListener innerhalb eines Effects Zuhörer hinzufügen und diese bei der Bereinigung wieder entfernen mussten. React 19.3 fügt onFullscreenChange und onFullscreenError als reguläre Props hinzu (#34621).
Die Demo benötigt einen Ref für das Element, das in Vollbildmodus geht, sowie einen Zustandswert für das Ereignisprotokoll.
import { useRef, useState } from 'react';
Beachten Sie, dass beide Handler direkt auf dem div liegen. Der Änderungs-Handler prüft document.fullscreenElement, um festzustellen, ob das Element gerade in den Vollbildmodus eingetreten oder diesen verlassen hat, da das Ereignis selbst in beide Richtungen ausgelöst wird. Die Schaltfläche „Vollbild wechseln“ fügt außerdem ein .catch zur von requestFullscreen() zurückgegebenen Promise hinzu, da eine Ablehnung sowohl als abgelehnte Promise als auch als Fehlerereignis gemeldet wird.
export default function App() {
const box = useRef(null);
const [log, setLog] = useState([]);
const add = line => setLog(l => [...l, line]); return (
<div
ref={box}
className="stage"
onFullscreenChange={() => add(document.fullscreenElement ? 'entered fullscreen' : 'left fullscreen')}
onFullscreenError={e => add('fullscreen error: ' + e.type)}
>
<button onClick={() => box.current.requestFullscreen().catch(err => add('rejected: ' + err.message))}>
Go fullscreen
</button>{' '}
<button onClick={() => document.fullscreenElement && document.exitFullscreen()}>Exit</button>
<pre className="log">{log.join('\n') || 'no events yet'}</pre>
</div>
);
}
Die Stylesheet verwendet die :fullscreen-Pseudo-Klasse, um den Inhalt zu zentrieren, während die Bühne den gesamten Bildschirm ausfüllt, sowie light-dark(), um einen Hintergrund auszuwählen, der zum Farbschema passt.
.stage { padding: 12px; border-radius: 10px; background: light-dark(#f1f5f9, #262a33); }
.stage:fullscreen { display: grid; place-content: center; }
.log { margin: 10px 0 0; font-size: 14px; }
onFullscreenError wird ausgelöst, wenn der Browser die Anfrage ablehnt. Die beiden häufigsten Ursachen sind der Aufruf von requestFullscreen() außerhalb eines Benutzerhandlungsablaufs sowie die Ausführung innerhalb eines <iframe>, der keine Einstellung allow="fullscreen" enthält. Wenn Sie einen Player auf einer Seite Dritter einbetten und der Vollbildmodus nicht funktioniert, ist diese fehlende iframe-Erlaubnis das Erste, was überprüft werden sollte – die neue Eigenschaft bietet außerdem einen geeigneten Ort zur Protokollierung.
Übergänge warten nicht mehr aufeinander
Laut dem Changelog wird jeder Übergang nun eigenständig dargestellt, anstatt mit anderen zu einem gemeinsamen Render zusammengefasst zu werden, sodass ein langsamer Übergang keine anderen, unverwandten Übergänge blockieren kann. Die Änderung (#37290) ermöglicht die Flagge enableParallelTransitions.
Vor Version 19.3 wurden, wenn während React noch die erste startTransition-Aktualisierung ausführte, weitere solche Aktualisierungen zusammengeführt. Die laufende Renderung wurde mit beiden Aktualisierungen neu gestartet, und keines der Updates konnte umgesetzt werden, bis die kombinierte Renderung abgeschlossen war. Dadurch konnte eine schnelle, günstige Transition durch eine teure Transition, mit der sie nichts zu tun hatte, blockiert werden. Heute ist jede Transition eine eigenständige Arbeitseinheit. Wenn Sie verstehen möchten, wie React diese Einheiten intern verfolgt, behandelt unser Artikel zu wie React-Lanes die Priorität von Aktualisierungen kodieren das zugrundeliegende Modell.
Die Art und Weise, wie Sie Transitions schreiben, ändert sich nicht, und Reacts Sortiergarantien gelten weiterhin: Eine spätere Aktualisierung wird niemals zusammen mit einem älteren Zustand gespeichert. Eine Annahme gilt jedoch nicht mehr: Aktualisierungen, die über zwei verschiedene startTransition-Aufrufe erfolgen, können nun in unterschiedlichen Frames abgelegt werden. Wenn mehrere Aktualisierungen zusammen angezeigt werden müssen, platzieren Sie sie in einem einzigen startTransition-Aufruf.
Erwarten Sie das nicht in einem einfachen Beispiel. Ein Versuch, dies mit einem langsamen Baum aus 300 Komponenten sowie einer trivialen, schnellen Aktualisierung, die 50 ms später geplant war, nachzustellen, zeigte, dass die schnelle Aktualisierung sowohl in Version 19.2 als auch in 19.3 nach der langsamen gespeichert wurde. Die Verbesserung liegt auf Ebene des Schedulers und zeigt sich in großen Anwendungen unter echter Last, nicht in kleinen Demos.
Formulare: onReset nach einer Aktion und neue Fehlerbeschreibungen
Wenn eine Form-Aktion abgeschlossen ist, setzt React die Form automatisch zurück. Bisher führte er diese Rücksetzung mit deaktiviertem Ereignissystem durch, wodurch das reset-Ereignis ignoriert wurde. Ein Handler onReset, der dazu diente, den kontrollierten Zustand zu löschen, wurde nie ausgeführt, und die kontrollierten Eingabefelder zeigten weiterhin ihre vorherigen Werte an (#35176, was das Problem #33630 behebt). In Version 19.3 wird onReset auch beim automatischen Rücksetzen ausgelöst. Wenn Sie das Problem umgangen haben, indem Sie selbst auf das native reset-Ereignis gelauscht haben, können Sie zu dieser Eigenschaft zurückkehren und den Effekt löschen.
Eine damit verbundene ästhetische Änderung: Die Fehlermeldungen von useActionState beziehen sich nun auf „Action-Zustand“ statt auf „Form-Zustand“ (#35790). Das Verhalten bleibt unverändert, doch Suchanfragen in den Logs, Warnregeln oder Tests, die noch auf die alte Formulierung abzielen, müssen aktualisiert werden.
Änderungen zur Entwicklung: Strict Mode, use() und Fast Refresh
Drei Änderungen betreffen ausschließlich Entwicklungskompilierungen, können jedoch nach einem Upgrade zu überraschenden Problemen führen.
- Strict Mode während der Hydratierung. Die Effekte werden nun beim Hydratisieren im Strict Mode doppelt aufgerufen (#35961), was dem Verhalten von client-seitig renderierten Anwendungen seit React 18 entspricht. Wenn eine serverseitig renderierte App bisher nur deshalb als sicher im Strict Mode galt, weil die Hydratierung den zweiten Aufruf übersprungen hat, wird dies nun offengelegt. Die Lösung ist die übliche: Die Effekte müssen idempotent gemacht und eine ordnungsgemäße Bereinigung vorgesehen werden.
use(). Es ist zulässig, use bedingungsweise aufzurufen. Das problematische Szenario tritt auf, wenn eine Komponente bei einem Aufruf von use() pausiert wird und beim erneuten Versuch einen Pfad wählt, der use() überhaupt nicht aufruft. Dadurch wird die Suspense-Grenze aus dem falschen Grund gelöst. React gibt nun eine Entwicklungswarnung aus, die erklärt, dass eine Bibliothek, die in einem früheren Render durch use() pausiert wurde, diesen Aufruf nach Abschluss übersprungen hat – was eine falsche Verwendung des Hooks darstellt. Die Formulierung deutet darauf hin, dass der Übeltäter oft eine Datenbibliothek und nicht Ihre eigene Komponente ist (#37104, #37203, #37491).lazy() geladen wird, eine geänderte Vergleichsfunktion von memo(), sowie eine Änderung, die eine Komponente in eine andere Art von Komponente umwandelt (#36965, #36964, #36963, #36950). Wenn solche Änderungen manchmal zu einem vollständigen Seitenneuladen führten, lagen die Ursachen darin.Kleinere DOM-Änderungen
resize-Aktualisierungen werden nun bis zum nächsten Frame gebündelt (#35117), genauso wie es bereits bei Scroll-Events der Fall ist. Der im Resize-Listener gesetzte Zustand wird nicht mehr frühzeitig ausgeführt, nur weil ein Klick mitten im Frame erfolgt.maskTypewird nun als SVG-Attribut erkannt (#35921).- Module-Ressourcen können nun eine Prioritätsangabe enthalten: Übergeben Sie
fetchPriorityanReactDOM.preloadModuleoderReactDOM.preinitModule, und React rendernt dies alsfetchpriority-Attribut (#36835). Dadurch kann ein RSC-Bundler-Plugin Client-Component-Chunks als niedrig priorisiert kennzeichnen. Die nicht-modularen Funktionenpreloadundpreinitunterstützen dies bereits. credentiallesswird als boolesches Attribut in<iframe>behandelt (#36148), und React schlägt die korrekte Schreibweise vor, falls SiecredentialLessverwenden (#36322).- React weist
innerHTMLnicht mehr zu, wenn sich dieser nicht geändert hat (#36949). Eine erneute Renderung mit dem identischendangerouslySetInnerHTMLveranlasst den Browser nicht mehr, die Markup-Struktur erneut zu parsen, wodurch auch der darin enthaltene DOM-Zustand erhalten bleibt. defaultValuebei Eingabefeldern mittype="number"verhält sich nun genauso wie bei anderen Eingabetypen (#36980).- Das
toggle-Event kopiert diesource-Eigenschaft für Popovers (#37389).
Serverseitige Darstellung und Server Components
- Fehler, die auf der Serverseite ausgelöst werden, erreichen nun den Client zusammen mit ihrer
cause(#35810), und einAggregateErrorbehält seinerrors-Array bei (#36156). Fehlergrenzen sowie Logging-Code, die die Fehlerkette durchlaufen, sehen auf beiden Seiten dieselbe Struktur. - Server Components können nun
<Activity>darstellen, da Flight dieses Element versteht (#34697). react-dom/serverhängt nun nicht mehr unter Deno (#35235), und der Einstiegspunkt von Bun enthält nun alle Node-APIs (#34193).
Fehlerbehebungen, deren Symptome Sie möglicherweise bereits gesehen haben
Die vollständige Liste ist lang. Dies sind die Einträge, die am ehesten auf einen Fehler zutreffen, den Sie bereits untersucht haben.
useDeferredValuekönnte an einem veralteten Wert hängen bleiben (#36134). Ein verzögerter Wert, der gelegentlich nie aktualisiert wurde, war ein React-Fehler und kein Fehler in Ihrem Code.- Context-Änderungen erreichten nicht immer die Suspense-Fallbacks (#36160) oder einen Unterbaum hinter einer Grenze, der noch in Suspense war (#35839).
useEffectEventinforwardRef- undmemo-Komponenten liest veraltete Werte (#34831).- Während ein
<Activity>-Baum versteckt war, überginguseSyncExternalStoreStore-Änderungen (#36947). Zudem stoppt ein versteckter Baum das Hochheben seines<title>-Elements (#34983), hält seine ausgelösten Fehler außerhalb der sichtbaren Benutzeroberfläche (#35074) und zeigt nicht mehr an, was er in Portalen darstellt (#35091).
nonce-Attributen (#37030).<ViewTransition> wurden behoben: einer in Mobile Safari (#35337) und ein weiterer, wenn dieser mit SuspenseList verwendet wurde (#35520). Dies ist relevant, wenn Sie die experimentellen Versionen getestet haben.decodeReplyFromBusboy zum Verlust von FormData-Einträgen (#36468), tiefe asynchrone Abfolgen konnten den Stack überlaufen (#35612), und Debug-Informationen konnten exponentiell anwachsen, bis schließlich ein RangeError auftrat (#37481).Die vollständige Liste der Pull Requests ist im React CHANGELOG verlinkt.
Welchen Speicherbedarf die Aktualisierung in Bytes hat
Kurz nach der Veröffentlichung wies ein Kommentar in der r/reactjs-Diskussion auf einen Anstieg von react-dom-client.production.js von 523,5 kB auf 610,5 kB hin und fragte nach dem Grund. Ein Vergleich der auf unpkg veröffentlichten Versionen 19.2.0 und 19.3.0 ergibt folgende Werte:
cjs/react-dom-client.production.js, in Rohform: 536.016 B in 19.2.0 gegenüber 625.168 B in 19.3.0, was einem Anstieg von 89 kB (16,6 %) entspricht.- Das gleiche Datei mit gzip auf Stufe 9: 94,7 kB gegenüber 110,5 kB, was einem Anstieg von 15,7 kB entspricht.
cjs/react-dom-server.node.production.js, in Rohform: 277.046 B gegenüber 300.544 B, was einem Anstieg von 23,5 kB entspricht.cjs/react.production.js, in Rohform: 17.217 B gegenüber 18.040 B, was einem Anstieg von 0,8 kB entspricht.
Die Zahlen in den Reddit-Kommentaren bezeichnen dieselben Größenangaben, ausgedrückt in KiB. Der Grund ist einfach: View Transitions, Fragment Refs, browser() sowie der Codepfad für Trusted Types wurden hinter Feature Flags versteckt, die sie aus der stabilen Version entfernt haben. In Version 19.3 sind sie wieder enthalten. Über das Netzwerk beträgt der Unterschied etwa 16 kB im Gzip-Format, was mit einem mittelgroßen Icon-Set vergleichbar ist.
Das ist ein echter Kostenfaktor, den man im Leistungsbudget berücksichtigen sollte – doch es handelt sich dabei nicht um eine Verschlechterung im Verhalten von React. Wenn die zusätzlichen Bytes für Ihre Anwendung relevant sind, sind effektivere Maßnahmen in der Regel das Code-Splitting, das Reduzieren eigener Abhängigkeiten sowie das Aufschieben nicht kritischer Client-Komponenten, anstatt den Upgrade-Prozess zu verzögern.
Haupterkenntnisse
- Lesen Sie
e.submitterdirekt in denonSubmit-Handlern; die Form-Aktionen haben bereits dennameundvalueder Schaltfläche erhalten. - Ersetzen Sie manuelle
fullscreenchange-Listener durchonFullscreenChangeundonFullscreenErrorund handhaben Sie auch die abgelehnte Promise vonrequestFullscreen(). - Mehrere
startTransition-Aufrufe können nun in verschiedenen Frames ausgeführt werden, daher sollten Updates, die zusammen angezeigt werden müssen, gruppiert werden. onResetwird schließlich nach der automatischen Reset-Aktion einer Aktion ausgeführt, und im Strict Mode werden Effekte während der Hydratierung doppelt aufgerufen, daher sollte die Bereinigung der Effekte erneut überprüft werden.- Mehrere seit längerem bestehende Fehler, einschließlich eines blockierten
useDeferredValueund veralteteruseEffectEvent-Werte, wurden behoben; überprüfen Sie, ob in Ihrem Code noch Workarounds erforderlich sind.
react-dom auf der Client-Seite, wobei die zuvor als problematisch markierten Funktionen nun in stabilen Builds enthalten sind.