React- und JavaScript-Interviewantworten, die echte Tiefe demonstrieren.
Entdecken Sie stärkere, differenziertere Antworten auf häufige React- und JavaScript-Interviewfragen – von dem Virtual DOM bis zum Systemdesign –, die ein tieferes ingenieurtechnisches Urteilsvermögen zeigen.
Einführung: Warum die meisten Kandidaten gleich klingen
Besuchen Sie genügend React-Interviews, und Sie werden ein Muster erkennen: Immer wieder tauchen dieselben vorgefertigten Aussagen auf. „Der Virtual DOM ist schneller.“ „useEffect dient für Nebeneffekte.“ „JavaScript läuft auf einem einzigen Thread.“ Keine dieser Aussagen ist falsch, aber es handelt sich dabei um Dinge, die jeder nach dem Überfliegen einiger Blogartikel auswendig aufsagen kann – und sie verraten dem Interviewer nichts darüber, wie Sie tatsächlich denken.
Was einen starken Kandidaten von jemandem unterscheidet, der nur eine höfliche Absage-E-Mail erhält, ist nicht, ob er die aus dem Lehrbuch bekannte Definition kennt – sondern ob er eine Ebene tiefer gehen kann. Können Sie ein Konzept mit den damit verbundenen Abwägungen in Verbindung bringen? Können Sie erklären, warum eine bestimmte Designentscheidung getroffen wurde – und nicht nur, was sie bewirkt? Genau diese Fähigkeit prüfen Interviewer eigentlich.
Dieser Leitfaden geht auf die Fragen ein, die tatsächlich in Vorstellungsgesprächen für Entwickler mittlerer und höherer Ebene auftauchen, und liefert Antworten, die ausreichend detailliert sind, damit der Interviewer aufhört, nur in seinen Notizen zu blättern, und wirklich zuhört.
Abschnitt 1: Kernkonzepte von React
Frage 1: „Erklären Sie den Virtual DOM. Wie funktioniert er?“
Die leicht zu vergessende Antwort: „Der Virtual DOM ist eine leichte Kopie des echten DOMs. React vergleicht die beiden und aktualisiert nur das, was sich geändert hat, wodurch es schneller wird.“
Die bessere Antwort: Der Virtual DOM ist eine Abstraktion, doch ihn rein als „schneller“ darzustellen, verfehlt den eigentlichen Punkt. Der tatsächliche Leistungsvorteil ergibt sich nicht vom Virtual DOM selbst – er entsteht durch die um ihn herum aufgebauten Logiken zur Gruppierung von Änderungen und zum Abgleich.
React führt intern zwei Bäume: einen, der derzeit auf dem Bildschirm angezeigt wird, und einen Arbeitsbaum, der das darstellt, was gleich gerendert werden soll. Wenn sich der Zustand ändert, eilt React nicht sofort daran, den echten DOM zu verändern. Stattdessen erstellt es den neuen virtuellen DOM-Baum, führt eine Abgleichsprüfung durch, um die kleinste mögliche Anzahl an notwendigen Änderungen zu ermitteln, und wendet anschließend alle diese Änderungen in einem einzigen Schritt auf den echten DOM an. Genau dieser Schritt der Gruppierung verhindert wiederholte Neuberechnungen des Layouts, was oft als „Layout Thrashing“ bezeichnet wird.
Es gibt auch eine Nuance, die erwähnt werden sollte: Der Virtual DOM ist kein kostenloses Gut. Sein Vergleichsalgorithmus wird absichtlich bei einer Komplexität von O(n) gehalten, indem auf Heuristiken zurückgegriffen wird – beispielsweise unter der Annahme, dass Elemente unterschiedlicher Typen völlig verschiedene Unterbäume erzeugen werden – anstatt einen vollständig allgemeinen O(n³)-Baumvergleich durchzuführen. Dieser Kompromiss sorgt in den üblichen Fällen für eine schnelle Ausführung, bedeutet aber, dass bestimmte Szenarien, wie beispielsweise eine riesige Liste, in der sich nur ein einziger Eintrag ändert, dennoch ressourcenaufwendig sein können. Genau deshalb existieren Tools wie React.memo, useMemo sowie Bibliotheken zur Virtualisierung von Listen.
Auch ist es wichtig anzuerkennen, dass der Virtual DOM an Attraktivität als Alleinstellungsmerkmal verliert. Compiler wie der von Svelte überspringen den Schritt mit dem Virtual DOM ganz, und React selbst entwickelt Funktionen für paralleles Rendering, die die Funktionsweise der Abgleichprozesse im Hintergrund verändern. Der Virtual DOM löste um das Jahr 2013 herum ein spezifisches Problem. Zu wissen, warum er ursprünglich eingeführt wurde, ist wichtiger als die Mechanismen auswendig zu können.
Auf diese Weise zu antworten funktioniert, weil es historisches Bewusstsein, eine ehrliche Anerkennung von Kompromissen sowie Kenntnisse darüber zeigt, wie sich die gesamte Frontend-Welt weiterentwickelt hat – man beantwortet nicht nur die wörtliche Frage, sondern demonstriert auch, dass man versteht, wo diese Idee in den größeren Zusammenhang passt.
Frage 2: „Was ist der Unterschied zwischen useEffect, useLayoutEffect und wann würde man jeweils welche verwenden?“
Die vergessliche Antwort: „useEffect wird nach dem Render ausgeführt. useLayoutEffect wird vor dem Zeichnen durch den Browser ausgeführt. Verwenden Sie useLayoutEffect, wenn Sie die DOM-Struktur messen müssen.“
Die bessere Antwort: Der zeitliche Unterschied ist allgemein bekannt, doch die Erklärung, warum dieser Zeitpunkt wichtig ist, macht eine gute Antwort aus. useEffect wird asynchron ausgelöst, nachdem der Browser bereits den Bildschirm gezeichnet hat. useLayoutEffect hingegen wird synchron ausgelöst, direkt nachdem React die DOM-Änderungen berechnet hat, aber noch bevor der Browser Zeit hat zu zeichnen.
Dieser Unterschied bedeutet, dass useLayoutEffect tatsächlich die visuelle Aktualisierung blockiert. Wenn man aufwändige Berechnungen darin ausführt, wird der Benutzer einen eingefrorenen Bildschirm wahrnehmen. Genau deshalb empfiehlt die React-Dokumentation, standardmäßig auf useEffect zurückzugreifen – unnötiges Blockieren des Zeichnens ist eine häufige Leistungsfallstrick.
Trotzdem gibt es berechtigte Gründe, useLayoutEffect zu nutzen, und zwar über das reine „Messen des DOMs“ hinaus. Ein gutes Anwendungsbeispiel ist die Verhinderung sichtbarer Flackern. Stellen Sie sich vor, Sie rendern ein Tooltip, dessen Position von den Abmessungen eines Zielelements abhängt – die Durchführung dieser Positionsrechnung innerhalb von useEffect verursacht ein sichtbares Aufleuchten, bei dem das Tooltip kurz an der falschen Stelle erscheint, bevor es an den richtigen Platz springt. Die Durchführung derselben Rechnung innerhalb von useLayoutEffect vermeidet dieses Aufleuchten vollständig, da sie vor dem Zeichnen von Inhalten durch den Browser stattfindet.
In dieser Familie gibt es noch einen dritten Hook, den viele Entwickler übersehen: useInsertionEffect. Sein Zweck besteht darin, es Tools für CSS-in-JS zu ermöglichen, Style-Regeln vor dem Zeitpunkt in das Dokument einzufügen, an dem sonst die Layout-Effekte veraltete Style-Informationen aus dem DOM lesen würden. Die meisten Entwickler werden ihn niemals direkt benötigen, doch allein die Kenntnis, dass er Teil des Effect-Lebenszyklus von React ist, zeigt ein tieferes Verständnis dafür, wie React 18 Effekte im Allgemeinen handhabt.
Ein Interviewer könnte nachhaken und fragen, was passiert, wenn man useLayoutEffect während des Server-Seiten-Renderings verwendet. Die Antwort lautet: React warnt einen, da auf dem Server kein DOM zur Messung verfügbar ist. Der Hook wird während des SSR einfach nicht ausgeführt, weshalb jede Logik, die vom DOM abhängt, entweder einen ausschließlich clientseitigen Schutz benötigt oder in useEffect verlegt werden sollte.
Frage 3: „Erklären Sie das Renderungsverhalten von React. Wann wird eine Komponente neu gerendert?“
Die leicht zu vergessende Antwort: „Eine Komponente wird neu gerendert, sobald sich ihr Zustand oder ihre Props ändern.“
Die ausführlichere Antwort: Das ist nur die Oberfläche. Die interessantere Frage ist, was eigentlich als „Änderung“ gilt und was React tut, sobald es eine solche feststellt.
Eine Komponente wird unter drei Bedingungen neu gerendert:
- Ihr eigener lokaler Zustand ändert sich, in der Regel über einen State-Setter
- Ihre Elternkomponente wird neu gerendert, unabhängig davon, ob die weitergegebenen Props tatsächlich geändert wurden
- Ein Context-Wert, den sie verwendet, ändert sich
Die entscheidende Erkenntnis ist der zweite Punkt: React vergleicht die Props nicht, bevor es entscheidet, ob ein Kindskomponente neu gerendert werden soll. Wenn eine Elternkomponente gerendert wird, werden laut Konzept auch ihre Kindskomponenten gerendert. Der Vergleich der Props ist rechenintensiv, und in den meisten tatsächlichen Fällen muss die Kindskomponente ohnehin aktualisiert werden, weshalb das Auslassen dieses Vergleichs standardmäßig ein vernünftiger Kompromiss ist.
Deshalb greifen Entwickler oft fälschlicherweise zu React.memo. Die Memoisierung hat selbst ihren Preis – React muss bei jeder Renderung dennoch einen Vergleich der Props durchführen. Wenn es sich um komplexe Objekte handelt oder die umschließende Komponente ohnehin günstig zu rendern ist, kann das Einhüllen in React.memo die Leistung sogar verschlechtern anstatt zu verbessern.
Die wahre Fähigkeit besteht darin zu wissen, wann eine Optimierung tatsächlich notwendig ist. Eine nützliche Faustregel lautet, mit dem Speichern von Ergebnissen zu warten, bis ein echtes Problem gemessen wurde. Verwenden Sie zunächst den React DevTools Profiler, um echte Engpässe zu finden, und wenden Sie erst danach React.memo, useMemo oder useCallback gezielt an. Eine vorzeitige Optimierung in React bedeutet in der Regel, gegen das Design des Frameworks zu arbeiten anstatt mit ihm.
Durch die parallele Darstellung in React 18 kommt noch eine weitere Ebene hinzu. Darstellungen können nun unterbrochen, priorisiert oder sogar mitten im Vorgang verworfen werden. Zu erkennen, dass eine Darstellung nicht immer direkt zu einer synchronen DOM-Änderung führt, ist entscheidend, um Code zu schreiben, der bei paralleler Darstellung korrekt funktioniert.
Frage 4: Wie sollten Sie die Zustandsverwaltung in einer großen React-Anwendung handhaben?
Eine schwache Antwort betrachtet dies als eine Frage der Tooling-Strategie: Man greift zu Redux für alles Globale und verwendet useState für alles Lokale.
Eine stärkere Antwort beginnt damit, zu prüfen, um welche Art von Zustand es sich handelt und wer tatsächlich davon abhängt, bevor eine Bibliothek ausgewählt wird.
Zustände in einer großen Anwendung fallen im Allgemeinen in vier Kategorien:
- Lokaler UI-Zustand: Formwerte, Schalter, ob ein Modal geöffnet ist. Hier reicht
useStateaus. - Server-Zustand: Daten, die von einem Backend abgerufen werden. React Query oder SWR sind dafür konzipiert, da sie bereits Caching, Deduplizierung, Hintergrundabruf sowie optimistische Aktualisierungen bewältigen – Fähigkeiten, für die Redux nie entwickelt wurde.
Ein häufiger Fehler ist es, von Anfang an auf Redux zurückzugreifen. Redux eignet sich hervorragend für wirklich komplexe Client-Seiten-Logik mit vielen miteinander verbundenen Aktualisierungen, doch die meisten Anwendungen haben dieses Problem gar nicht – was wie Client-Zustand aussieht, ist oft nur versteckter Server-Zustand. Die Speicherung von API-Antworten in Redux ist vergleichbar damit, ein Bild aufzuhängen mit einer Axt: Es geht zwar, erfordert aber weitaus mehr Aufwand, als die Aufgabe eigentlich verlangt.
Wenn Redux tatsächlich angebracht ist, ist die Kombination aus Redux Toolkit und RTK Query ein guter Ansatz. RTK Query übernimmt die Verantwortung für den Server-Zustand, wodurch Redux nur noch die tatsächlich komplizierte Client-Logik verwalten muss. Die Trennung dieser Aufgaben macht die gesamte Architektur übersichtlicher.
Abschnitt 2: Eingehende Betrachtungen zu JavaScript
Frage 5: Erklären Sie Schließfunktionen in JavaScript. Geben Sie ein praktisches Beispiel.
Eine oberflächliche Antwort beschreibt eine Closure einfach als eine Funktion, die Variablen aus ihrem umgebenden Scope speichert.
Eine tiefere Antwort verbindet dies mit dem lexikalischen Scope: Wenn eine Funktion erstellt wird, erfasst sie Referenzen auf die umliegenden Variablen zum Zeitpunkt ihrer Erstellung und behält den Zugriff darauf, selbst nachdem die Ausführung außerhalb dieses ursprünglichen Scope erfolgt ist.
Was einen guten Kandidaten ausmacht, ist die Fähigkeit, zu erklären, warum dieses Verhalten insbesondere in React von Bedeutung ist. Betrachten Sie ein Muster, das häufig zu Fehlern führt:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // Always logs 0
setCount(count + 1); // Resets to 1 every time
}, 1000);
}, []); // Empty deps = closure over initial count
}
Der in setInterval referenzierte count bleibt bei dem Wert stehen, der während der initialen Darstellung vorhanden war. Da die Funktion aufgrund des leeren Abhängigkeitsarrays nur einmal ausgeführt wird, wird diese Schließung niemals mit neueren Werten aktualisiert. Einfach count in die Abhängigkeitsliste aufzunehmen, ist ebenfalls keine richtige Lösung, da dadurch der Zeitintervall bei jeder Aktualisierung neu erstellt werden müsste. Die richtige Lösung besteht darin, die funktionale Aktualisierungsform setCount(c => c + 1) zu verwenden, wodurch gänzlich auf die veraltete Schließung verzichtet werden kann.
Closures verursachen außerdem Reibungsverluste mit Event-Listenern innerhalb von benutzerdefinierten Hooks. Immer dann, wenn ein Listener in useEffect hinzugefügt wird und auf den State zugreift, ist eine Closure beteiligt. Eine gängige Technik für einen useEventListener-ähnlichen Hook besteht darin, die Handler-Funktion in einem ref zu speichern, damit der Listener stets die aktuellste Version abrufen kann, ohne erneut hinzugefügt werden zu müssen.
Das macht Closures keineswegs zu etwas, was man vermeiden sollte – sie sind ein zentrales Mechanismus, den es zu beherrschen gilt. Modulemuster, private Variablen, Factory-Funktionen und Currying setzen alle auf sie. Die wichtige Fähigkeit besteht darin, genau zu erkennen, wann eine Closure gebildet wird, und sicherzustellen, dass sie den tatsächlich beabsichtigten Wert erfasst.
Frage 6: Was ist der Event-Loop? Erklären Sie Microtasks und Macrotasks.
Eine oberflächliche Antwort weist darauf hin, dass der Event-Loop asynchrone Aufgaben verarbeitet und dass Microtasks vor Macrotasks ausgeführt werden.
Eine ausführlichere Antwort erklärt, dass JavaScript in einem einzigen Thread ausgeführt wird und der Browser Konkurrenz durch den Event-Loop simuliert. Synchroner Code läuft auf dem Call Stack; wenn eine asynchrone Operation auftaucht, wird sie an eine Web API weitergeleitet – setTimeout, fetch, DOM-Events – und sobald diese Arbeit abgeschlossen ist, wird der zugehörige Callback in eine Warteschlange gelegt.
Die Besonderheit, die erwähnt werden sollte, ist, dass es nicht nur eine Warteschlange gibt. Makrotasks – setTimeout, setInterval, I/O-Operationen – gelangen in eine Warteschlange, während Mikrotasks – Promise.then, queueMicrotask, MutationObserver – in eine andere gelangen. Sobald der Aufrufstapel leer ist, leert der Event-Loop zunächst die gesamte Mikrotask-Warteschlange, bevor er auch nur einen einzigen Makrotask verarbeitet.
Dies birgt ein echtes Risiko: Mikrotasks können den Rest des Programms lahmlegen. Wenn ständig neue Mikrotasks rekursiv in die Warteschlange eingefügt werden, kommen die ausstehenden setTimeout-Callback-Funktionen niemals an die Reihe. Eine solche Situation kann eine Benutzeroberfläche einfrieren, wenn Promises in einer Schleife miteinander verknüpft werden und der Kontrolle nie wieder an den Browser zurückgegeben wird.
Dies hängt direkt damit zusammen, wie React Zustandsaktualisierungen in Batches ausführt. In React 18 werden Zustandsaktualisierungen unabhängig davon, woher sie stammen – innerhalb von setTimeout, in einer Promise oder in einem nativen Ereignishandler – automatisch gebatcht. Vor React 18 war das nicht der Fall; dort wurden Aktualisierungen, die innerhalb von setTimeout ausgelöst wurden, einzeln angewendet anstatt gruppiert. Das Verständnis des Event-Loops macht klar, warum das automatische Batching in React 18 bedeutend ist: Es nutzt die Microtask-Queue, sodass alle ausstehenden Aktualisierungen vor dem nächsten Render zusammen ausgeführt werden.
Auch könnte man Sie bitten, die Ausgabe eines kurzen Codeausschnitts wie diesem vorherzusagen:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
Die erwartete Antwort lautet: 1, 4, 3, 2 – synchronen Anweisungen werden zuerst ausgeführt, gefolgt von Microtasks wie dem Promise-Callback, und erst danach wird die durch setTimeout angestufte Macrotask ausgeführt.
Frage 7: „Erklären Sie this in JavaScript. Inwiefern unterscheidet es sich von anderen Sprachen?“
Die schwache Antwort: „this verweist auf das Objekt, das die Funktion aufgerufen hat.“
Die ausführliche Antwort: „this in JavaScript folgt einem dynamischen Scoping statt einem lexikalischen Scoping. In den meisten Sprachen wird self oder this bereits zum Zeitpunkt der Funktionserklärung festgelegt. JavaScript hingegen bestimmt es zum Zeitpunkt des Aufrufs, basierend darauf, wie die Funktion aufgerufen wird und nicht darauf, wo sie im Quellcode steht.“
„Es gibt eine Reihenfolge von vier Bindungsregeln:
- Neue Bindung:
new Foo()setztthisauf die neu erstellte Instanz - Eindeutige Bindung:
foo.call(obj),foo.apply(obj)oderfoo.bind(obj)zwingenthisdazu, das übergebene Objekt zu sein
obj.foo() setzt this auf obj.foo() lässt this im strengen Modus auf undefined stehen, ansonsten wird globalThis verwendet."Arrow-Funktionen durchbrechen dieses Muster absichtlich – sie erben this lexikalisch aus dem Umgebungsrahmen, in dem sie sich befinden. Genau deshalb nutzten Entwickler vor der Einführung von Hooks Arrow-Funktionen in klassenbasierten React-Komponenten: So konnten sie auf den Aufruf von .bind(this) im Konstruktor verzichten.
"React-Code, der auf Hooks basiert, berührt this selten direkt, da Komponenten nicht mehr Klassen sind. Dennoch taucht dieses Konzept wieder auf, wenn man veraltete Klassenkomponenten wartet, Drittanbieter-Bibliotheken integriert oder auf einen Interviewer trifft, der prüfen möchte, wie gut man die Grundlagen von JavaScript beherrscht. Eine häufige Fallstrick in der Praxis ist es, eine Objektmethode als Callback zu übergeben – beispielsweise obj.handleClick an einen Event-Listener weiterzugeben – was die implizite Bindung entfernt und dazu führt, dass this auf einen unerwarteten Wert verweist."
Frage 8: "Was sind JavaScript-Promises? Erklären Sie async/await."
Die schwache Antwort: "Promises verwalten asynchrone Aufgaben, und async/await ist lediglich syntaktischer Zucker darüber."
Die klare Antwort: „Eine Promise steht für einen Wert, der noch nicht existiert, aber letztendlich bereitgestellt wird. Sie ersetzt verwickelte Callback-Ketten durch eine kettbare Schnittstelle sowie eine einheitliche Art und Weise, Erfolg oder Misserfolg auszudrücken.“
„Was Promises wirklich nützlich macht, ist nicht die Syntax, sondern die dahinterstehenden Garantien. Sobald sich eine Promise entschieden hat – sei es erfüllt oder abgelehnt – bleibt dieses Ergebnis unveränderlich. Genau diese Unveränderlichkeit macht sie komponierbar und leichter verständlich.“
„Async/await als ‚nur Schleifchen‘ zu bezeichnen, unterschätzt dessen Bedeutung – es verändert grundlegend die Art und Weise, wie man asynchrone Logik schreibt, sodass sie wie synchroner Code aussieht und viel leichter nachvollzogen werden kann. Dennoch birgt es einige Fallstricke, auf die man achten muss.“
// This runs sequentially - 6 seconds total
async function sequential() {
const a = await fetch('/a'); // 3s
const b = await fetch('/b'); // 3s
}
// This runs in parallel - 3 seconds total
async function parallel() {
const [a, b] = await Promise.all([fetch('/a'), fetch('/b')]);
}
"Ein häufiger Fehler bei weniger erfahrenen Entwicklern besteht darin, await innerhalb einer Schleife zu verwenden, was unbeabsichtigt dazu führt, dass die Operationen nacheinander statt parallel ausgeführt werden. Die Lösung besteht darin, Promise.all zu verwenden, sobald die Operationen nicht voneinander abhängen, und eine for...of-Schleife mit await für Fälle zu nutzen, in denen eine strenge Abfolge tatsächlich erforderlich ist."
"Fehlerbehandlung ist ein weiterer Bereich, in dem Probleme auftreten können. Das Einbetten einer await-Aufruf in try/catch ermöglicht es, Ablehnungen abzufangen, doch das Auslassen dieses Rahmens kann dazu führen, dass eine unverarbeitete Ablehnung einen Node.js-Prozess abstürzen lässt. Im Frontend verhindern das Einbetten asynchroner Aufrufe in Fehlergrenzen oder der Einsatz einer Bibliothek wie React Query, die Fehlerzustände deklarativ verarbeitet, dieses Versagen."
Abschnitt 3: Systemdesign und Architektur
Frage 9: „Entwerfen Sie einen Echtzeit-Kollaborations-Texteditor wie Google Docs.“
Diese Frage verschiebt den Fokus des Interviews völlig. Der Interviewer fragt nicht mehr nach Ihren Kenntnissen zu React – er möchte sehen, wie Sie über Systemarchitekturen nachdenken.
Ein solider Ansatz sieht so aus:
„Bevor ich eine Zeile Code schreibe, würde ich zunächst die Anforderungen klären:
- Wie viele Personen bearbeiten gleichzeitig? Die Architektur für 10 gleichzeitige Benutzer sieht völlig anders aus als die für 10.000.
- Welche Latenz ist akzeptabel – echte Echtzeit oder eher etwas in Richtung nahezu Echtzeit?
- Muss die Anwendung auch offline funktionieren?
- Welche Konfliktlösungsstrategie werden wir anwenden?“
„Auf der Frontend-Seite:
- State Management: Jeder Client behält eine eigene lokale Kopie des Dokuments. Änderungen werden zunächst optimistisch auf dem Client vorgenommen und anschließend an den Server gesendet, der sie an alle anderen verbundenen Clients weiterleitet.
- Operational Transformation oder CRDTs: Dies ist das Verfahren zur Lösung konkurrierender Änderungen. OT war Googles ursprüngliche Technik, erfordert jedoch einen zentralen Server zur Entscheidungsfindung bezüglich der Reihenfolge. CRDTs (Conflict-free Replicated Data Types) können hingegen peer-to-peer arbeiten, und Tools wie Yjs haben sie immer beliebter gemacht.
requestAnimationFrame sowie Debouncing bei ausgehenden Netzwerk-Synchronisierungen, sodass nicht bei jeder Tastenbetätigung eine Anfrage gesendet wird."Auf der Synchronisationsebene:
- WebSocket übernimmt den Echtzeit-Transport
- Server-Sent Events oder Long-Polling dienen als Ersatz, falls eine WebSocket-Verbindung nicht hergestellt werden kann
- Präsenzdaten – wer online ist und wo sich der Cursor befindet – werden über einen eigenen, leichten Kanal übertragen, der vom Dokumentinhalt getrennt ist"
"Der wirklich schwierige Teil dieses Problems hat nichts mit der React-Renderung zu tun – es geht um das zugrunde liegende Konsistenzmodell. Was sollte passieren, wenn zwei Personen genau zur gleichen Zeit an derselben Cursorposition tippen? Die Antwort hängt vollständig davon ab, ob man OT oder CRDTs gewählt hat, und diese eine Entscheidung prägt fast alle weiteren architektonischen Entscheidungen."
Frage 10: "Wie optimiert man eine React-Anwendung, die langsam lädt und interagiert?"
Die enttäuschende Antwort: eine Aufzählung verschiedener Strategien – Memoisierung, lazy Loading, Bündel aufteilen – ohne jegliche Einordnung.
Die herausragende Antwort: „Ich würde mit Messungen anfangen statt zu raten. Der React DevTools Profiler sowie das Performance-Panel von Chrome zeigen an, ob das Problem in der Ladezeit, der Renderzeit oder beidem liegt – es macht keinen Sinn, blind zu optimieren.“
Was die Ladezeit angeht:
- Code-Splitting: Das basale Vorgehen ist das routebasierte Splitting mithilfe von React.lazy und Suspense, doch damit sollte es nicht enden. Schwere Komponenten, die nicht sofort sichtbar sind – wie Modale oder Inhalt unterhalb der Scrollleiste – verdienen ebenfalls eigene Split-Punkte.
- Vorladen: Verwenden Sie
<link rel="preload">für kritische Ressourcen und kombinieren Sie React.lazy mit Prefetch-Hinweisen für Routen, die der Benutzer wahrscheinlich als Nächstes besuchen wird.
import lodash from 'lodash' anstelle von import debounce from 'lodash/debounce' kann dazu führen, dass das finale Bündel um 100 KB kleiner wird.Was die Interaktion angeht:
- Virtualisierung: Sobald eine Liste etwa 50 Elemente umfasst, sollten Sie react-window oder react-virtualized verwenden. Das Rendern von 10.000 DOM-Elementen auf einmal wird niemals schnell wirken, egal wie effizient der Rest Ihres Codes ist.
- Eine disziplinierte Strategie der Memoisierung: Zuerst analysieren, dann handeln. Teure Berechnungen in
useMemo, teure Callbacks inuseCallbackund Komponenten, die unnötig neu gerendert werden, inReact.memoeinpacken. Die automatische Memoisierung aller Elemente ohne Messung der tatsächlichen Auswirkungen fügt in der Regel zusätzliche Last hinzu anstatt sie zu reduzieren. - Standortierung des States: Den State so nah wie möglich bei der Komponente halten, die ihn tatsächlich verwendet. Den State nur deshalb auf einen gemeinsamen Vorfahren hochziehen, weil dies ordentlicher erscheint, führt jedes Mal zu zusätzlichen Neuerendern, sobald sich der State ändert.
- Trennung von Contexts: Wenn ein einziger Context häufige Aktualisierungen – wie die Mausposition – mit seltenen Aktualisierungen – wie der Authentifizierungsstatus – mischt, sollte er in zwei getrennte Contexts aufgeteilt werden. Andernfalls zwingt jede Mausbewegung dazu, alle Verbraucherkomponenten neu zu rendern, auch diejenigen, die sich nur um die Authentifizierung kümmern.
Zu der wahrgenommenen Leistung:
- Skelettansichten anstelle von Ladeindikatoren verleihen einer Benutzeroberfläche das Gefühl von Geschwindigkeit, da der Inhalt schrittweise angezeigt wird und nicht auf einmal erscheint.
- Durch progressive Initialisierung mithilfe der
Suspense-Funktion in React 18 wird sicher gestellt, dass kritischer Inhalt zuerst initialisiert wird, während sekundäre Abschnitte danach folgen. - Die Messgröße „Interaction to Next Paint“, ein neueres Kriterium aus Google’s Core Web Vital, ist es wert, verfolgt zu werden. Sie ersetzt „First Input Delay“, da sie die Reaktionsfähigkeit über den gesamten Lebenszyklus der Seite misst und nicht nur bei der ersten Interaktion. Das Ziel ist es, dass Event-Handler innerhalb von weniger als 200 Millisekunden ausgeführt werden.
Zusätzliche Literatur
- Hindernisse in der Backend-Architektur, die Teams mit Frontend-First-Ansatz in React behindern — Erklärt fünf häufige Fehler im Backend-Design in React-basierten Projekten – von Fehlern bei der API-Nutzung bis hin zu instabilen Deployments – sowie die architektonischen Lösungen für eine zuverlässige Produktionsumgebung.
- Umgang mit realen UI-Zuständen mithilfe von bedingtem Rendering in React — Lernen Sie, wie Sie in React Authentifizierungs-, Rollen-, Berechtigungs-, Lade-, Fehler- und leere-Zustands-UIs mit praktischen Mustern des bedingten Renderings erstellen.