Startseite / Artikel

Erklärung zur React-Renderung: Zustandsaktualisierungen zu Bildschirmpixeln

Wie die Render-, Reconciliation- und Commit-Phasen von React mit dem Browser-Rendering verbunden sind: Layout, Paint und Composite, von einem Zustandsupdate bis zu den tatsächlichen Bildpunkten auf dem Bildschirm.

Verlag: REACTAPP.TOP17 Min. Lesezeit4015 Wörter

Quellen: Rendering Lifecycle in React: From State Update to Pixels on the Screen

Beim Arbeiten mit React hört man oft den Ausdruck „der Komponente wird neu gerendert“. Doch was bedeutet Rendering eigentlich im Hintergrund?

Dabei tauchen einige natürliche Fragen auf: Schickt React Updates an den DOM sobald sich der Zustand ändert? Zwingt eine Zustandsänderung jede einzelne Komponente dazu, neu gerendert zu werden? Und wenn React etwas neu rendernt, bedeutet das, dass der Browser sofort die ganze Seite neu zeichnet? Wie sich herausstellt, stimmt keines davon.

Um den Rendering-Lebenszyklus wirklich zu verstehen, muss man zwei voneinander unabhängige Systeme kennen, die zusammenarbeiten:

Reacts eigener Rendering-Lebenszyklus und der Rendering-Pipeline des Browsers.

Sobald man sieht, wie diese beiden Systeme miteinander verbunden sind, erscheinen Konzepte wie Vereinbarung, die Commit-Phase, useEffect, useLayoutEffect, Neuberechnung und Memoisierung nicht mehr wie Magie, sondern ergeben einen Sinn.

1. Das Gesamtbild

Auf höherer Ebene sieht der Ablauf so aus:

User Interaction
      │
      ▼
State / Props Change
      │
      ▼
┌──────────────────────┐
│     React Render     │
│                      │
│  Component executes  │
│  New UI is returned  │
└──────────┬───────────┘
           │
           ▼
     Reconciliation
           │
           ▼
┌──────────────────────┐
│     Commit Phase     │
│                      │
│  Update real DOM     │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────────┐
│    Browser Rendering     │
│                          │
│ Style → Layout → Paint   │
│           → Composite    │
└────────────┬─────────────┘
             │
             ▼
           Screen

Die wichtigste Erkenntnis ist folgende:

React bestimmt, wie die Benutzeroberfläche aussehen sollte und welche DOM-Änderungen notwendig sind. Der Browser wandelt anschließend diese DOM-Änderungen in tatsächliche Pixel auf dem Bildschirm um.

2. Was bedeutet „Rendering“?

Der Begriff Rendering wird in mehreren Bedeutungen verwendet.

React ruft Ihre Komponentenfunktion auf und bestimmt, wie die resultierende Benutzeroberfläche aussehen sollte.

Betrachten Sie diese Komponente:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button>
      Count: {count}
    </button>
  );
}

Wenn React es rendernt, wird der Funktionskörper ausgeführt:

Counter()
   ↓
returns React element
   ↓
<button>Count: 0</button>

Das bedeutet nicht, dass der Browser die Schaltfläche bereits auf dem Bildschirm dargestellt hat.

Bevor irgendetwas Sichtbares erscheint, muss React erst entscheiden, ob sich der tatsächliche DOM überhaupt ändern muss.

Kurz gesagt:

React Render
     ≠
DOM Update
     ≠
Browser Paint

Rendering und Aktualisierung des DOMs sind zwei unterschiedliche Vorgänge.

3. Rendering-Lebenszyklus des Browsers

Bevor man sich mit den Interna von React beschäftigt, ist es hilfreich zu verstehen, was der Browser selbst tut.

Stellen Sie sich folgende Markup-Struktur vor:

<h1 class="title">
  Hello World
</h1>

in Kombination mit dieser Stylesheet-Datei:

.title {
  color: red;
  font-size: 30px;
}

Bevor dieser Inhalt visuell angezeigt wird, durchläuft der Browser eine Reihe interner Schritte.

Eine vereinfachte Version dieses Ablaufs sieht so aus:

HTML
 ↓
DOM
 ↓
CSS
 ↓
CSSOM
 ↓
Render Tree
 ↓
Layout
 ↓
Paint
 ↓
Composite
 ↓
Screen

Lassen Sie uns jede Phase durchgehen.

4. HTML → DOM

Der Browser liest die HTML-Datei und erstellt das DOM, kurz für Document Object Model.

Angesichts dieser Markup-Struktur:

<body>
  <h1>Hello World</h1>
  <p>Welcome</p>
</body>

Der Browser erstellt eine Baumstruktur, die ungefähr so aussieht:

Document
   │
   └── body
       ├── h1
       │   └── "Hello World"
       │
       └── p
           └── "Welcome"

JavaScript kann anschließend diese Struktur direkt abfragen und verändern:

document.querySelector("h1");

Im Grunde beantwortet das DOM eine Frage:

Welche Elemente existieren auf der Seite?

5. CSS → CSSOM

Neben dem DOM analysiert der Browser auch Ihre CSS-Dateien und erstellt das CSSOM, also das CSS Object Model.

Zum Beispiel:

h1 {
  color: red;
  font-size: 30px;
}

Daraus ermittelt der Browser, welche Styles auf welche Elemente angewendet werden.

Konzeptionell sieht das so aus:

h1
 ├── color: red
 └── font-size: 30px

Das CSSOM beantwortet eine andere Frage:

Wie sollten die einzelnen Elemente visuell gestaltet werden?

6. DOM + CSSOM → Render Tree

Danach verschmilzt der Browser das DOM mit dem CSSOM, um einen Render-Tree zu erzeugen.

Es gibt eine Feinheit, die erwähnenswert ist:

Nicht jeder Knoten im DOM wird schließlich im Render-Tree dargestellt.

Nehmen wir zum Beispiel diese Markup-Struktur:

<div class="hidden">
  Hello
</div>

In Kombination mit dieser Regel:

.hidden {
  display: none;
}

Das div-Element ist im DOM vorhanden:

DOM
 └── div

Aber da es folgendes enthält:

display: none;

wird es vollständig aus dem visuellen Render-Tree ausgeklammert.

7. Layout

Sobald der Browser genau weiß, welche Elemente angezeigt werden müssen, berechnet er ihre Geometrie.

Dazu gehören die Berechnung von Dingen wie:

  • Breite
  • Höhe
  • Position
  • Ränder
  • Padding
  • Koordinaten
  • Wie sich die Elemente räumlich zueinander verhalten

Nehmen wir diese Regel als Beispiel:

.box {
  width: 200px;
  height: 100px;
  margin-left: 50px;
}

Der Browser muss konkrete Werte in dieser Form erzeugen:

x      = 50px
y      = ...
width  = 200px
height = 100px

Dieser Berechnungsschritt wird als Layout bezeichnet und wird auch häufig als Reflow bezeichnet.

8. Paint

Sobald das Layout abgeschlossen ist, verfügt der Browser über Koordinaten für jedes sichtbare Element.

Der nächste Schritt besteht darin, auszumalen, wie diese Elemente tatsächlich aussehen.

Betrachten Sie folgende Styling-Einstellungen:

.box {
  background: blue;
  color: white;
  border: 2px solid black;
}

Das Paint-Verfahren umfasst Dinge wie:

  • Hintergrund
  • Text
  • Ränder
  • Schatten
  • Bilder
  • Weitere visuelle Elemente

In dieser Reihenfolge:

Layout
  ↓
Paint
  ↓
Pixels

9. Composite

Heutige Browser können eine Seite in mehrere separate Schichten aufteilen.

Zum Beispiel:

Layer 1 → Page background
Layer 2 → Header
Layer 3 → Content
Layer 4 → Modal

Dann fügt der Kompositor des Browsers all diese Schichten zu dem einzigen Bild zusammen, das schließlich auf dem Bildschirm angezeigt wird.

Bestimmte CSS-Eigenschaften, wie zum Beispiel:

transform
opacity

Es kann in der Regel allein durch Kompositing animiert werden, wodurch die aufwändigere Layout-Arbeit vermieden wird, die Eigenschaften erfordern, die die Geometrie beeinflussen.

Deshalb ist das Animieren von transform in der Regel der bevorzugte Ansatz.

10. Reacts Rendering-Lebenszyklus

Nun, da die Browser-Seite erläutert wurde, schauen wir uns an, wie React das Rendering handhabt.

Ein nützliches mentales Modell teilt diesen Prozess in vier Phasen ein:

1. Trigger
2. Render
3. Reconciliation
4. Commit
5. Browser Rendering

Lassen Sie uns jede Phase einzeln durchgehen.

11. Phase 1 – Auslöser

Bevor irgendetwas anderes geschieht, benötigt React ein Signal, das folgendes signalisiert:

"Die Benutzeroberfläche könnte aktualisiert werden müssen."

Dieses Signal wird als Rendering-Auslöser bezeichnet.

Typische Auslöser sind:

1. Zustandsaktualisierung: setCount(10);

2. Änderung der Props: <Child count={count} />

Wenn ein Elternkomponente mit einem neuen Wert für count erneut gerendert wird, kann seine Kindkomponente dadurch neue Props erhalten.

3. Kontextaktualisierung: useContext(MyContext);

Jedes Mal, wenn sich der aus dem Kontext gelesene Wert ändert, müssen die Komponenten, die ihn verwenden, möglicherweise aktualisiert werden.

4. Aktualisierung externer Speicher: Bibliotheken und APIs können ebenfalls React-Updates auslösen.

Beispiele sind:

Redux
Zustand
useSyncExternalStore

Auch das Erstmalige Laden einer Anwendung zählt als Auslöser.

12. Phase 2 – Rendern

Sobald React eine Aktualisierung erkennt, wechselt es in die Render-Phase, in der es Komponenten ausführt, um das neue UI zu bestimmen.

Nehmen wir dieses Beispiel:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Zu Beginn beträgt count 0. React ruft Counter() auf und erzeugt:

<button>
  Count: 0
</button>

Sobald auf die Schaltfläche geklickt wird und dies ausgeführt wird:

setCount(c => c + 1);

count wird auf 1 gesetzt, und React führt die Komponente erneut aus, wodurch Folgendes entsteht:

<button>
  Count: 1
</button>

13. Wichtig: Rendern bedeutet nicht automatisch eine DOM-Änderung

Das ist ein entscheidender Unterschied im React-Modell.

Wenn React diese Funktion ausführt:

function Counter() {
  console.log("Counter rendered");

  return <button>Count: 1</button>;
}

wird lediglich berechnet, wie die Benutzeroberfläche sollte aussehen. Das bedeutet nicht automatisch, dass sich der DOM geändert hat oder dass der Browser etwas neu gemalt hat.

Die tatsächliche Abfolge sieht so aus:

Component executes
       ↓
React creates new element representation
       ↓
Reconciliation
       ↓
Determine required DOM changes
       ↓
Commit changes

Dies führt zu einer wichtigen Regel:

Das Rendern einer Komponente führt nicht automatisch zu einer DOM-Änderung.

14. Phase 3 – Abgleich

In dieser Phase vergleicht React die alte und neue Benutzeroberfläche, um genau herauszufinden, was sich geändert hat.

Nehmen wir an, der vorherige Baum sah so aus:

button
 └── "Count: 0"

Und der neue Baum sieht so aus:

button
 └── "Count: 1"

React kann herausfinden:

button → same
text   → changed

Deshalb muss es nur den Teil des DOMs anfassen, der tatsächlich geändert wurde:

button.textContent = "Count: 1";

Der Hauptzweck besteht darin, den gesamten DOM-Baum nicht abzureißen und neu aufzubauen, wenn sich nur ein kleiner Teil geändert hat.

15. Schlüssel und Abgleich

Schlüssel sind besonders wichtig, wenn React mit Listen arbeitet.

Zum Beispiel:

items.map(item => (
  <li key={item.id}>
    {item.name}
  </li>
))

React verlässt sich auf Schlüssel, um zu erkennen, welches Element bei jeder Neuzeichnung welches ist. Angenommen, man beginnt mit:

Before:

A
B
C

und kommt schließlich zu:

After:

A
C

Mit stabilen Schlüsseln kann React richtig ableiten:

A → same
B → removed
C → same

Das liefert React viel zuverlässigere Informationen für den Abgleich. Vermeiden Sie es, auf Array-Indizes als Schlüssel zu vertrauen, wenn die Listeneinträge umsortiert, hinzugefügt oder entfernt werden könnten.

16. Phase 4 – Commit

Sobald React herausgefunden hat, was geändert werden muss, geht es in die Commit-Phase, in der diese Änderungen tatsächlich im echten DOM angewendet werden.

Stellen Sie sich ein Button-Element vor, dessen Anzeige von einer Null auf eine Eins wechselt, sobald diese Phase abgeschlossen ist.

Zu diesem Zeitpunkt stimmen Reacts interner Baum und der aktuelle DOM endlich überein.

Die Commit-Phase ist ein separater Schritt von der Render-Phase:

Render Phase
    ↓
Calculate what should change

Commit Phase
    ↓
Actually apply DOM changes

17. Render-Phase gegen Commit-Phase

Das Verständnis dieser Trennung ist eines der häufigsten Themen, die bei Vorstellungsgesprächen untersucht werden.

Render-Phase

Während dieser Phase führt React folgende Schritte aus:

  • Führt Komponentenfunktionen aus
  • Berechnet die nächste Version der Benutzeroberfläche
  • Führt Abgleichsprozesse durch
  • Ermittelt, was geändert werden muss
Component
   ↓
React elements
   ↓
Reconciliation

Commit-Phase

Während dieser Phase führt React folgende Schritte aus:

  • Führt die tatsächlichen DOM-Mutationen durch
  • aktualisiert alle Referenzen
  • führt die mit dieser Aktualisierung verbundenen Layout-Effekte aus
React result
    ↓
Real DOM

Hier ist eine vereinfachte Darstellung des Ablaufs:

             React
               │
               ▼
        ┌──────────────┐
        │ Render Phase │
        └──────┬───────┘
               │
               ▼
        ┌───────────────┐
        │ Commit Phase  │
        └──────┬────────┘
               │
               ▼
            Real DOM

18. Was passiert nach dem Commit?

Sobald die Commit-Phase abgeschlossen ist, übernimmt der Browser und stellt fest, dass sich das DOM geändert hat.

Je nach Art der Änderung muss der Browser möglicherweise mehrere Schritte durchlaufen:

DOM Changes
    ↓
Style Calculation
    ↓
Layout
    ↓
Paint
    ↓
Composite

Nicht jede Änderung erfordert alle diese Schritte, um sinnvolle Arbeit zu leisten – eine Änderung benötigt beispielsweise nur einen Neuanstrich, ohne dass eine vollständige Layout-Neuberechnung nötig ist.

Genau an diesem Punkt übergeben Reacts eigener Lebenszyklus den Prozess an den zuvor beschriebenen Rendering-Pipeline des Browsers.

19. useLayoutEffect

useLayoutEffect wird ausgelöst, nachdem React seine DOM-Änderungen vorgenommen hat, aber bevor der Browser die neuen Darstellungen zeichnen kann.

Die Abfolge sieht so aus:

React Render
     ↓
Commit
     ↓
DOM updated
     ↓
useLayoutEffect
     ↓
Browser Paint
function Tooltip() {
  const ref = useRef(null);

  useLayoutEffect(() => {
    const rect = ref.current.getBoundingClientRect();

    console.log(rect.width);
  }, []);

  return (
    <div ref={ref}>
      Tooltip
    </div>
  );
}

Dieser Hook ist das richtige Werkzeug, wenn Sie Folgendes benötigen:

  • Elemente im DOM messen
  • aktuelle Layout-Werte abrufen
  • visuelle Anpassungen synchron vornehmen, bevor der Benutzer etwas sieht

Eine Vorsichtsmaßnahme: Wenn die Arbeit innerhalb von useLayoutEffect aufwendig ist, kann dies die Zeichnung verzögern und dazu führen, dass die Benutzeroberfläche träge wirkt.

20. useEffect

useEffect hingegen ist für Effekte gedacht, die nicht die Zeichnung des Browsers blockieren müssen.

useEffect(() => {
  fetch("/api/products");
}, []);
Render
   ↓
Commit
   ↓
Browser Paint
   ↓
useEffect

Die tatsächlichen Planungsdetails sind etwas subtiler als das hier beschriebene, aber die wichtigste praktische Regel lautet:

Nutzen Sie useLayoutEffect nur dann, wenn etwas vor dem Zeichnen durch den Browser geschehen muss; verwenden Sie useEffect für alles, was auch nachdem die UI bereits sichtbar ist, funktionieren kann.

21. Vollständiges Beispiel

import { useEffect, useLayoutEffect, useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  console.log("Render");

  useLayoutEffect(() => {
    console.log("Layout Effect");
  });

  useEffect(() => {
    console.log("Effect");
  });

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Konzeptionell sieht die Abfolge der Ereignisse so aus:

User clicks button
        ↓
setCount()
        ↓
State update scheduled
        ↓
Counter() executes
        ↓
"Render"
        ↓
React reconciles
        ↓
React commits DOM update
        ↓
"Layout Effect"
        ↓
Browser paints updated UI
        ↓
"Effect"

22. Verursacht eine Neuzusammenstellung des Elternteils eine Neuzusammenstellung des Kindes?

Nehmen Sie dieses Beispiel:

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <Child />
    </>
  );
}

Wenn count aktualisiert wird:

Parent renders
       ↓
Child is encountered
       ↓
Child may render as part of reconciliation

So,

Parent state update
      ↓
Parent render
      ↓
Child can render

Dieses Verhalten ist einer der Gründe, warum React.memo als Optimierungstool existiert.

23. React.memo

Nehmen wir an, Sie haben:

const Child = React.memo(function Child() {
  console.log("Child rendered");

  return <p>Hello</p>;
});

Falls das Elternteil neu zusammengestellt wird, aber die an das Kind übergebenen Props referenziell unverändert bleiben, kann React die Neuzusammenstellung dieses gememorisierten Kindes vollständig überspringen.

So sieht die Idee visuell aus:

Parent update
     ↓
Parent renders
     ↓
React.memo checks Child props
     ↓
Props unchanged
     ↓
Skip Child render

Aber bedenken Sie Folgendes:

React.memo bietet einen Leistungsbeschleuniger – es garantiert jedoch nicht, dass eine Komponente niemals erneut gerendert wird.

Änderungen im Kontext, der interne Zustand sowie andere Auslöser können die Aktualisierung trotz Memoisierung erzwingen.

24. useMemo und useCallback

Diese beiden Hooks werden häufig falsch verstanden.

useMemo: Es memoisiert einen berechneten Wert.

const result = useMemo(() => expensiveCalculation(data), [data]);

useCallback: Es memoisiert eine Funktionseintragung.

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

Sowohl Funktionen als auch Eintragungen sind in Situationen wirklich nützlich, in denen referenzielle Gleichheit dazu dient, wiederholte Arbeiten zu vermeiden – insbesondere beim Übergeben von Props an memoisierte Kindkomponenten.

Trotzdem führt das Standardmäßige Einsatz in jedem Codeabschnitt in der Regel zu mehr Komplexität, ohne echte Leistungsverbesserungen zu bringen.

25. Eine React-Renderung ist nicht dasselbe wie das Zeichnen im Browser

Dieser Punkt sollte ausdrücklich erwähnt werden.

Stellen Sie sich folgendes Komponentenbeispiel vor:

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>
        {count}
      </button>

      <p>Hello</p>
    </div>
  );
}

Wenn sich count ändert:

App renders
     ↓
New React tree created
     ↓
Reconciliation
     ↓
Only count text differs
     ↓
Commit small DOM update
     ↓
Browser rendering

React baut diese Markup-Struktur nicht unbedingt von Grund auf neu auf:

<div>
  <button>...</button>
  <p>Hello</p>
</div>

26. Virtual DOM – Das richtige mentale Modell verstehen

Eine häufige Behauptung ist, dass React auf einem Virtual DOM setzt, um den Browser zu beschleunigen. Das ist eine Vereinfachung.

Was React tatsächlich tut, ist, eine interne Beschreibung der Benutzeroberfläche im Speicher zu halten und mithilfe von Reconciliation herauszufinden, was sich geändert hat.

Der Browser selbst kennt kein Konzept eines Virtual DOM. Er arbeitet nur mit Elementen wie HTML, dem DOM, CSS und letztendlich Pixeln.

Konzeptionell sieht der Ablauf so aus:

React Component
      ↓
React Element Tree
      ↓
Reconciliation
      ↓
DOM Mutations
      ↓
Browser
      ↓
Pixels

Der Virtuelle DOM ist niemals etwas, was der Browser tatsächlich darstellt.

27. Neudarstellung vs. Neuzeichnen

Es handelt sich um zwei unterschiedliche Konzepte, die nicht miteinander verwechselt werden sollten.

React-Neudarstellung

Dies tritt ein, wenn eine Komponentenfunktion erneut ausgeführt wird – im Grunde genommen wird Component() ausgerufen.

Browser-Neuzeichnen

Dies geschieht, wenn der Browser die Pixel auf dem Bildschirm neu zeichnet, weil sich eine visuelle Eigenschaft geändert hat – das ist der Paint-Schritt.

Denken Sie daher daran: eine React-Neudarstellung ist nicht gleichbedeutend mit einem Browser-Neuzeichnen.

Es ist durchaus möglich, dass eine Komponente erneut dargestellt wird, aber dennoch dieselbe DOM-Struktur liefert. Umgekehrt kann der Browser aus Gründen neu zeichnen, die nichts mit der Darstellung einer React-Komponente zu tun haben.

28. Umlayout vs. Neuzeichnen

Nehmen wir dieses Beispiel: element.style.width = „500px“;

Die Änderung der Breite eines Elements zwingt in der Regel zu einer erneuten Berechnung des Layouts:

Style
 ↓
Layout
 ↓
Paint
 ↓
Composite

Vergleichen wir das nun mit: element.style.backgroundColor = „red“;

Hier muss der Browser in der Regel keine geometrischen Berechnungen erneut durchführen:

Style
 ↓
Paint
 ↓
Composite

Bei bestimmten Änderungen der Transformation oder Transparenz muss der Browser möglicherweise nur:

Composite

Es ist sehr wichtig, diesen Unterschied zu verstehen, wenn man Animationen oder Benutzeroberflächen mit hohen Renderanforderungen optimiert.

29. Anwendung in einer echten React-Situation

Stellen Sie sich eine typische E-Commerce-Produktseite vor, die wie folgt strukturiert ist:

Product Page
 ├── Navbar
 ├── Product Gallery
 ├── Product Information
 ├── Add To Cart
 ├── Reviews
 └── Recommendations

Nehmen wir an, ein Benutzer klickt auf die Schaltfläche Zum Warenkorb hinzufügen, wodurch die Anzahl im Warenkorb aktualisiert wird.

Konzeptionell könnte die Abfolge der Ereignisse so aussehen:

User clicks "Add to Cart"
           ↓
Cart state updates
           ↓
React schedules update
           ↓
Relevant components render
           ↓
React reconciles new tree
           ↓
Only required DOM changes are committed
           ↓
Browser performs necessary rendering work
           ↓
Updated cart count appears

In einer gut strukturierten Anwendung sollte diese Aktualisierung der Warenkorbanzahl nicht unnötigerweise dazu führen, dass die gesamte Benutzeroberfläche neu erstellt wird. Genau deshalb sind Entscheidungen bezüglich der Grenzen der Komponenten, des Ortes, an dem der Zustand gespeichert wird, der Memoisierung sowie der Gesamtrendering-Architektur von großer Bedeutung.

30. Wenn teures Rendering zu einem Leistungsproblem wird

function ProductList({ products }) {
  return products.map(product => (
    <ExpensiveProductCard
      key={product.id}
      product={product}
    />
  ));
}

Nehmen wir an, diese Liste zeigt 10.000 Produkte an, und eine nicht verwandte Zustandsänderung veranlasst unnötige Arbeiten in einer großen Anzahl von Komponenten – die Leistung kann deutlich nachlassen.

Häufige Abmilderungstechniken sind:

React.memo
      +
Stable props
      +
useMemo/useCallback where justified
      +
Virtualization
      +
Pagination
      +
Code splitting

Bei besonders großen Listen ist Virtualisierung in der Regel besonders wirksam:

10000 items
     ↓
Only visible ~20-50 items rendered

31. Wie Rendering mit koncurrentem React zusammenwirkt

Neuere Versionen von React bieten fortgeschrittenere Planungsfunktionen. React kann verschiedenen Aktualisierungen Prioritäten zuweisen und bei Bedarf die Render-Arbeit pausieren oder verschieben.

Das wichtigste Denkmuster, das man beibehalten sollte, ist:

Render work
    ↓
Can be scheduled
    ↓
Can be interrupted in modern React
    ↓
Eventually committed

Genau deshalb sollten Nebeneffekte nicht innerhalb der Render-Logik einer Komponente liegen:

function Component() {
  fetch("/api/data"); // ❌ Side effect during render

  return <div>Hello</div>;
}

Stattdessen sollte man einen Effect-Hook oder ein geeignetes Muster zur Datenerfassung verwenden. Idealerweise sollte die Render-Phase rein bleiben.

32. Der Vorteil einer reinen Renderung

Konzeptionell sollte sich eine React-Komponente wie eine reine Funktion verhalten:

UI = f(props, state)

Bei denselben Props und State sollte sie immer dieselbe Benutzeroberfläche darstellen. Zum Beispiel:

function Greeting({ name }) {
  return <h1>Hello {name}</h1>;
}

Dieses Verhalten ist vorhersehbar und sicher.

Dagegen ist es problematisch, etwas Derartiges innerhalb des Renders zu tun:

function Component() {
  localStorage.setItem("x", "1"); // ❌
  fetch("/api");                  // ❌

  return <div>Hello</div>;
}

Das führt zu Nebeneffekten in einer Phase, die von React möglicherweise mehrmals ausgeführt oder auf unerwartete Weise geplant werden kann.

Nebeneffekte gehören an einen anderen Ort – innerhalb von Effects, Event-Handlern oder den Datenabrufmechanismen, die Ihr Framework bereitstellt.

33. Rendering-Lebenszyklus – Vollständiges Denkmodell

                    USER ACTION
                         │
                         ▼
               State / Props Update
                         │
                         ▼
                React Schedules Work
                         │
                         ▼
                 ┌──────────────┐
                 │    RENDER    │
                 │              │
                 │ Components   │
                 │ execute      │
                 └──────┬───────┘
                        │
                        ▼
                 Reconciliation
                        │
                        ▼
                 ┌──────────────┐
                 │    COMMIT    │
                 │              │
                 │ DOM mutation │
                 └──────┬───────┘
                        │
                        ▼
                 useLayoutEffect
                        │
                        ▼
              Browser Rendering
                        │
                        ▼
                 Style Calculation
                        │
                        ▼
                     Layout
                        │
                        ▼
                      Paint
                        │
                        ▼
                   Composite
                        │
                        ▼
                      SCREEN
                        │
                        ▼
                   useEffect

Übliche Vorstellungsgesprächsfragen

Q1. Aktualisiert eine Zustandsänderung sofort den DOM?

Nicht unbedingt. Eine Zustandsänderung plant lediglich die Arbeit für React an. React rendernt das Komponente, vergleicht die Ausgabe mit der vorherigen und führt erst dann die tatsächlich notwendigen DOM-Änderungen durch.

setState
  ↓
Render
  ↓
Reconciliation
  ↓
Commit
  ↓
DOM

Q2. Führt jedes Render zu einer DOM-Änderung?

Nicht immer. React kann ein Komponente rendern und dennoch feststellen, dass die resultierende UI-Beschreibung identisch mit dem Ist-Zustand auf dem Bildschirm ist; in diesem Fall wird der DOM unberührt gelassen.

Render
  ↓
Compare
  ↓
No meaningful DOM difference
  ↓
No DOM mutation

Q3. Zwingt das Neuerstellen der Darstellung in React den Browser dazu, neu zu zeichnen?

Nicht unbedingt. Die Darstellung in React und das Zeichnen im Browser sind zwei separate Prozesse.

React Render ≠ Browser Paint

Q4. Was bedeutet Reconciliation?

Reconciliation ist der Prozess, bei dem React den neu berechneten UI- Baum mit dem vorherigen vergleicht, um genau herauszufinden, was geändert werden muss.

Q5. Was passiert in der Commit-Phase?

Während der Commit-Phase wendet React die erforderlichen Änderungen im Host-Umfeld an – beispielsweise indem es die tatsächlichen DOM-Node im Browser aktualisiert.

Q6. Inwiefern unterscheiden sich useEffect und useLayoutEffect?

Eine einfache Erinnerungshilfe:

useLayoutEffect
→ after DOM commit, before paint

useEffect
→ after commit and generally after paint

Wenden Sie useLayoutEffect an, wenn Sie das Layout synchron messen oder anpassen müssen, bevor der Benutzer den neuen Frame sieht. Verwenden Sie useEffect für die meisten Nebeneffekte, bei denen kein Blockieren der Darstellung erforderlich ist.

Q7. Was löst die Erneutdarstellung einer Komponente aus?

Typische Auslöser sind: eine anfängliche Initialisierung, ein Zustandsupdate, eine erneute Darstellung eines Elternteils, eine Änderung der Props, ein Update des Contexts oder ein Update aus einem externen Speicher.

Q8. Wie kann die Darstellung optimiert werden? [IMP]

Anstatt standardmäßig überall useMemo zu verwenden, ermitteln Sie zunächst genau, wo das eigentliche Engpass ist. Betrachten Sie anschließend diese Ansätze:

1. Halten Sie den Zustand in der Nähe dessen, wo er verwendet wird

Vermeiden Sie eine Struktur, in der sich der Zustand an der Spitze befindet und alle Nachkommen davon abhängen:

App
 ↓
Everything depends on state

prefer:

Feature
 ↓
Local state

Es ist besser, einen engeren Platz einzunehmen, wenn der globale Zustand tatsächlich nicht notwendig ist.

2. Teure Komponenten selektiv memorisieren

const ProductCard = React.memo(ProductCardComponent);

3. Achten Sie auf Objekterstellung, die die Memorisierung stört

Anstatt immer wieder ein neues Objekt direkt hier einzufügen:

<Child config={{ theme: "dark" }} />

Man benötigt möglicherweise eine stabile Referenz, wenn Child in React.memo eingebettet ist und die Identität wichtig ist – aber beheben Sie das erst, wenn es tatsächlich ein Problem darstellt.

4. Lange Listen virtualisieren

Anstatt alle 10.000 DOM-Elemente auf einmal anzuzeigen, zeigen Sie nur die derzeit sichtbaren an.

5. Das Laden aufwändiger Funktionen verzögern

const AdminDashboard = lazy(() => import("./AdminDashboard"));

Dadurch verringert sich die Menge an JavaScript, die im Voraus heruntergeladen und ausgeführt werden muss.

6. Browserfreundliche Rendering-Techniken bevorzugen

Bewegen Sie Elemente möglichst mit günstigen Eigenschaften wie Transformations- und Transparenz-Einstellungen, und vermeiden Sie teure Neuberechnungen des Layouts in jedem Frame.