useLayoutEffect vs. useEffect: Timing-Unterschiede wirklich verstehen
AI generated
{ }
React 19 · Rendering Pipeline · Hooks
useLayoutEffect vs. useEffect
Warum der Zeitpunkt der Ausfuehrung ueber sichtbares Flackern entscheidet

useEffect laeuft nach dem Paint, useLayoutEffect synchron davor. Ein winziger Timing-Unterschied auf dem Papier, der in der Praxis entscheidet, ob eine DOM-Messung fuer einen Tooltip oder eine Scroll-Position sichtbar flackert oder nicht.

12 Min. Lesezeit useLayoutEffect · useEffect Rendering Pipeline

1. Die Rendering-Pipeline in drei Phasen

React's Rendering-Pipeline laeuft in drei aufeinanderfolgenden Phasen ab: In der Render-Phase berechnet React den neuen virtuellen Baum, in der Commit-Phase wendet React die daraus resultierenden Aenderungen tatsaechlich auf das echte DOM an, und in der Paint-Phase zeichnet der Browser diese DOM-Aenderungen schliesslich als Pixel auf den Bildschirm.

useEffect und useLayoutEffect greifen an unterschiedlichen Punkten in Bezug auf diese dritte Phase ein. useEffect wird asynchron nach dem Paint ausgefuehrt, useLayoutEffect dagegen synchron nach dem Commit, aber noch bevor der Browser ueberhaupt zeichnet. Dieser scheinbar kleine Unterschied entscheidet in der Praxis, ob eine DOM-Messung fuer den Nutzer sichtbares Flackern verursacht oder nicht.

2. useEffect: Ausfuehrung nach dem Paint

useEffect plant seine Callback-Ausfuehrung ueber die Scheduling-Queue des Browsers ein. Der Browser darf dazwischen bereits den aktuellen Frame zeichnen, bevor der Effekt ueberhaupt laeuft, dadurch bleibt der Haupt-Thread fuer den Nutzer durchgehend reaktionsfaehig. Fuer Datenabruf, Subscriptions, Event-Listener-Registrierung oder Logging, also fuer Effekte ohne unmittelbare visuelle Auswirkung, ist das der ideale und empfohlene Standardfall.

Der Preis fuer diese Nicht-Blockierung zeigt sich, sobald im useEffect eine DOM-Eigenschaft gelesen und darauf basierend das Layout veraendert wird, etwa die tatsaechliche Hoehe eines gerade gerenderten Tooltips. Der Browser hat zu diesem Zeitpunkt bereits den alten, noch falschen Zustand gezeichnet, die anschliessende Korrektur wird deshalb als kurzes, aber deutlich sichtbares Flackern wahrgenommen.


function NaiveTooltip({ triggerRef, children }) {
  const [top, setTop] = useState(0);

  useEffect(() => {
    // Diese Messung laeuft NACH dem ersten sichtbaren Paint,
    // der Tooltip erscheint kurz an falscher Position und springt danach
    const rect = triggerRef.current.getBoundingClientRect();
    setTop(rect.bottom + 8);
  }, [triggerRef]);

  return <div style={{ position: 'fixed', top }}>{children}</div>;
}

3. useLayoutEffect: synchron vor dem Paint

useLayoutEffect wird synchron ausgefuehrt, direkt nachdem React die DOM-Mutationen in der Commit-Phase angewendet hat, aber noch bevor der Browser den naechsten Frame zeichnet. React wartet dabei bewusst mit dem Paint, bis die Callback-Funktion vollstaendig abgeschlossen ist.

Dadurch koennen innerhalb von useLayoutEffect Messungen wie getBoundingClientRect() oder offsetHeight durchgefuehrt werden, und ein darauf reagierender erneuter setState-Aufruf wird noch vor dem sichtbaren Frame verarbeitet. Der Nutzer bekommt den Zwischenzustand mit der falschen Position nie zu sehen, weil er niemals gezeichnet wird.


function CorrectTooltip({ triggerRef, children }) {
  const [top, setTop] = useState(0);

  useLayoutEffect(() => {
    // Diese Messung laeuft VOR dem sichtbaren Paint,
    // der resultierende setState-Aufruf wird noch im selben Zyklus verarbeitet
    const rect = triggerRef.current.getBoundingClientRect();
    setTop(rect.bottom + 8);
  }, [triggerRef]);

  return <div style={{ position: 'fixed', top }}>{children}</div>;
}

4. Ein konkretes Flacker-Szenario

Ein Tooltip soll direkt oberhalb seines Trigger-Elements erscheinen, seine tatsaechliche Hoehe ist jedoch erst bekannt, nachdem er mindestens einmal gerendert wurde. Mit useEffect erscheint der Tooltip deshalb zunaechst an einer angenommenen, noch falschen Position und springt anschliessend sichtbar an die tatsaechlich korrekte Stelle, sobald die Messung abgeschlossen ist.

Je nach Bildschirm-Refresh-Rate und Komplexitaet des Tooltip-Inhalts kann dieses Springen als deutliches, stoerendes Flackern wahrgenommen werden. Besonders auffaellig wird es bei schnell aufeinanderfolgenden Interaktionen, etwa beim Hovern ueber mehrere Listenelemente hintereinander, wo mehrere solcher Positionssprünge innerhalb kurzer Zeit sichtbar aufeinanderfolgen.

5. Die Loesung mit useLayoutEffect

Wird dieselbe Messung von useEffect in useLayoutEffect verschoben, berechnet React die korrekte Position bereits innerhalb desselben Commit-Zyklus. Der resultierende setState-Aufruf loest einen zusaetzlichen, aber weiterhin synchronen Render- und Commit-Durchlauf aus, bevor der Browser ueberhaupt etwas auf den Bildschirm zeichnet.

Der Nutzer sieht dadurch direkt die korrekt positionierte Tooltip, ohne sichtbaren Zwischenzustand und ohne wahrnehmbaren Sprung. Der Trade-off dabei ist, dass React fuer diesen zusaetzlichen synchronen Zyklus den Paint verzoegert, was bei aufwendigen Messungen oder tief verschachtelten Komponentenbaeumen durchaus spuerbar werden kann und deshalb bewusst dosiert eingesetzt werden sollte.


function Tooltip({ triggerRef, children }) {
  const tooltipRef = useRef(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    const triggerRect = triggerRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();

    let top = triggerRect.bottom + 8;
    if (top + tooltipRect.height > window.innerHeight) {
      top = triggerRect.top - tooltipRect.height - 8; // oberhalb ausweichen
    }
    setPos({ top, left: triggerRect.left });
  }, [triggerRef]);

  return (
    <div ref={tooltipRef} style={{ position: 'fixed', top: pos.top, left: pos.left }}>
      {children}
    </div>
  );
}

6. Server-Side-Rendering und die useLayoutEffect-Warnung

Auf dem Server existiert weder ein DOM noch ein Paint-Zyklus. React gibt deshalb bei useLayoutEffect in einer serverseitig gerenderten Komponente eine Warnung aus, sinngemaess: useLayoutEffect does nothing on the server. Der Effekt wird dort schlicht nie ausgefuehrt, weil es keinen Commit-Zeitpunkt im Browser-Sinn gibt, auf den sich useLayoutEffect beziehen koennte.

Ein verbreitetes Muster dagegen ist ein eigener useIsomorphicLayoutEffect-Hook, der auf dem Server auf useEffect zurueckfaellt, das dort ebenfalls nicht laeuft, aber keine Warnung erzeugt, und im Browser regulaer useLayoutEffect verwendet. Die Entscheidung erfolgt ueblicherweise ueber eine Pruefung von typeof window !== 'undefined' beim Modul-Import.


import { useEffect, useLayoutEffect } from 'react';

// Faellt auf dem Server auf useEffect zurueck (keine Warnung),
// nutzt im Browser das synchrone useLayoutEffect
export const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

7. Weitere sinnvolle Einsatzfelder

Neben Tooltip-Positionierung eignet sich useLayoutEffect fuer das Sichern und Wiederherstellen der Scroll-Position rund um ein DOM-Update, etwa bei einer Chat-Liste, die neue Nachrichten oben einfuegt und bei der die bisherige Scroll-Position sonst sichtbar nach oben springen wuerde. Auch das synchrone Auslesen von ResizeObserver-Messwerten vor dem ersten sichtbaren Frame gehoert in diese Kategorie.

Ein weiterer klassischer Fall sind CSS-Transition-Trigger, bei denen zunaechst ein Startzustand gesetzt, das DOM committed und unmittelbar danach im selben Frame der Zielzustand gesetzt werden muss, damit der Browser die Transition ueberhaupt als Animation und nicht als Sprung erkennt und korrekt abspielt.


function ChatList({ messages }) {
  const listRef = useRef(null);
  const prevHeight = useRef(0);

  useLayoutEffect(() => {
    const el = listRef.current;
    const newHeight = el.scrollHeight;
    // Scroll-Position um genau die neu eingefuegte Hoehe verschieben,
    // noch bevor der Browser den neuen Zustand zeichnet
    el.scrollTop += newHeight - prevHeight.current;
    prevHeight.current = newHeight;
  }, [messages]);

  return <div ref={listRef} className="chat-list">{/* ... */}</div>;
}

8. Faustregel: Wann ist useLayoutEffect wirklich noetig

Wird im Effekt nichts vom DOM gelesen, das synchron ein weiteres Layout-Update ausloesen soll, reicht useEffect vollkommen aus. Die ueberwiegende Mehrheit aller Effekte in einer typischen Anwendung, Datenabruf, Subscriptions, Event-Listener-Registrierung, Analytics und Logging, gehoert in diese Kategorie und profitiert von der Nicht-Blockierung des Haupt-Threads durch useEffect.

useLayoutEffect ist nur dann tatsaechlich noetig, wenn ein sichtbarer Zwischenzustand, sei es Flackern oder ein sichtbarer Sprung, andernfalls unvermeidbar waere, weil eine DOM-Messung das Ergebnis eines synchronen State-Updates direkt beeinflusst. In allen anderen Faellen kostet der Einsatz von useLayoutEffect lediglich unnoetig wahrgenommene Rendering-Performance, ohne einen erkennbaren Nutzen zu bieten.

9. Haeufige Fehler im Umgang mit useLayoutEffect

Der haeufigste Fehler ist, useLayoutEffect pauschal anstelle von useEffect zu verwenden, ohne dass ueberhaupt eine echte Layout-Messung stattfindet. Dadurch blockiert jeder Commit unnoetig den Paint, was bei komplexen, tief verschachtelten Komponentenbaeumen die wahrgenommene Reaktionsgeschwindigkeit der gesamten Anwendung spuerbar verschlechtert, obwohl kein einziger sichtbarer Vorteil dadurch entsteht.

Ein zweiter verbreiteter Fehler sind asynchrone Operationen wie fetch-Aufrufe oder Timer innerhalb von useLayoutEffect, was dessen synchronen Vorteil vollstaendig zunichtemacht, weil der Browser ohnehin auf die asynchrone Operation warten muesste. Dazu kommt haeufig fehlendes Cleanup bei Messungen, die an Resize- oder Scroll-Events gekoppelt sind, wodurch veraltete Listener nach dem Unmount der Komponente still weiterlaufen.

Kriterium useEffect useLayoutEffect Empfehlung
Ausfuehrungszeitpunkt Asynchron, nach dem Paint Synchron, nach Commit, vor dem Paint useEffect als Standardfall waehlen
Blockiert den Browser-Paint Nein Ja, bis die Callback abgeschlossen ist useLayoutEffect sparsam einsetzen
Verhalten bei Server-Side-Rendering Laeuft nicht auf dem Server, keine Warnung Laeuft nicht auf dem Server, erzeugt eine Warnung useIsomorphicLayoutEffect bei SSR-Projekten nutzen
Typischer Einsatzzweck Datenabruf, Subscriptions, Logging DOM-Messung mit sofort folgendem Layout-Update Nach dem tatsaechlichen Bedarf entscheiden, nicht pauschal

Mironsoft

React-Architektur, Performance und Magento-Frontend-Integration

React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?

Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.

Performance-Audit

Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.

State-Architektur

Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.

Magento-Integration

GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.

10. Zusammenfassung

useLayoutEffect vs. useEffect: Das Wichtigste auf einen Blick

Timing

useEffect laeuft asynchron nach dem Paint, useLayoutEffect synchron nach dem Commit, aber vor dem Paint.

Flackern

DOM-Messungen mit sofort folgendem Layout-Update gehoeren in useLayoutEffect, um sichtbare Spruenge zu vermeiden.

SSR

useLayoutEffect erzeugt auf dem Server eine Warnung, useIsomorphicLayoutEffect loest das Problem sauber.

Faustregel

Ohne echte Layout-Messung reicht useEffect, useLayoutEffect ist die Ausnahme, nicht der Standardfall.

11. FAQ: useLayoutEffect vs. useEffect: Das Wichtigste auf einen Blick

1Was ist der grundlegende Unterschied zwischen useEffect und useLayoutEffect?
useEffect wird asynchron nach dem Paint ausgefuehrt, useLayoutEffect synchron nach dem Commit, aber noch bevor der Browser den Frame zeichnet. Dieser Timing-Unterschied entscheidet, ob eine DOM-Messung sichtbares Flackern verursacht oder nicht.
2Warum flackert ein Tooltip, dessen Position in useEffect berechnet wird?
Weil useEffect erst nach dem Paint laeuft, hat der Browser zu diesem Zeitpunkt bereits die alte, noch falsche Position gezeichnet. Die anschliessende Korrektur wird deshalb als kurzer, sichtbarer Sprung wahrgenommen.
3Wie loest useLayoutEffect dieses Flacker-Problem?
useLayoutEffect laeuft synchron vor dem Paint, sodass eine Messung und der daraus folgende setState-Aufruf noch innerhalb desselben Zyklus verarbeitet werden. Der Browser zeichnet dadurch direkt die korrekte Position, ohne sichtbaren Zwischenzustand.
4Warum erzeugt useLayoutEffect auf dem Server eine Warnung?
Auf dem Server existiert kein DOM und kein Paint-Zyklus, an dem sich useLayoutEffect orientieren koennte. React warnt deshalb, dass der Effekt dort schlicht nie ausgefuehrt wird.
5Was ist useIsomorphicLayoutEffect und wann brauche ich ihn?
Ein Hook, der auf dem Server auf useEffect zurueckfaellt, um die Warnung zu vermeiden, und im Browser regulaer useLayoutEffect nutzt. Er ist bei Server-Side-Rendering sinnvoll, sobald eine Komponente ansonsten useLayoutEffect direkt verwenden wuerde.
6Blockiert useLayoutEffect wirklich den Browser?
Ja, React wartet mit dem Zeichnen des Frames, bis die useLayoutEffect-Callback vollstaendig abgeschlossen ist. Bei aufwendigen Berechnungen oder tief verschachtelten Baeumen kann das spuerbar werden, weshalb der Einsatz bewusst dosiert werden sollte.
7Fuer welche Faelle reicht useEffect vollkommen aus?
Fuer die grosse Mehrheit aller Effekte ohne unmittelbare visuelle Auswirkung, etwa Datenabruf, Subscriptions, Event-Listener-Registrierung, Analytics und Logging. Nur wenn eine DOM-Messung ein sofortiges Layout-Update verursacht, ist useLayoutEffect noetig.
8Kann ich asynchrone Operationen in useLayoutEffect ausfuehren?
Technisch ja, sinnvoll ist es aber nicht, weil der Browser ohnehin auf die asynchrone Operation warten muss und der synchrone Vorteil von useLayoutEffect dadurch vollstaendig verloren geht. Asynchrone Operationen gehoeren in useEffect.
9Welche Anwendungsfaelle abseits von Tooltips profitieren von useLayoutEffect?
Das Sichern und Wiederherstellen der Scroll-Position bei DOM-Updates, etwa bei einer Chat-Liste, das synchrone Auslesen von ResizeObserver-Messwerten sowie CSS-Transition-Trigger, bei denen Start- und Zielzustand im selben Frame gesetzt werden muessen.
10Was ist der haeufigste Fehler beim Einsatz von useLayoutEffect?
useLayoutEffect pauschal statt useEffect zu verwenden, ohne dass eine echte Layout-Messung stattfindet. Dadurch blockiert jeder Commit unnoetig den Paint, ohne dass ein erkennbarer Nutzen entsteht.