Menu

React useLayoutEffect: Messen, bevor der Browser zeichnet

useLayoutEffect funktioniert wie useEffect, läuft aber direkt nachdem React das DOM aktualisiert hat und bevor der Browser zeichnet. Nutze ihn, um ein Element zu messen und das Layout ohne sichtbares Flackern anzupassen, und behalte useEffect für alles andere.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

useLayoutEffect ist eine Version von useEffect, die früher läuft: direkt nachdem React das DOM aktualisiert hat, aber bevor der Browser den neuen Frame zeichnet. Code darin kann Elemente messen und das Layout ändern, und man sieht immer nur das Endergebnis. Er blockiert das Zeichnen, während er läuft, also nutze ihn nur, wenn du genau diesen Zeitpunkt brauchst.

Die Effekte sind in der Reihenfolge Effekt, Layout-Effekt deklariert, aber die Konsole gibt bei jedem Render zuerst den Layout-Effekt aus. Die Reihenfolge der Deklaration spielt zwischen den beiden Hooks keine Rolle; ihr Zeitpunkt schon.

Die Syntax

Die Signatur ist identisch mit useEffect: eine Setup-Funktion, ein optionales Cleanup und ein Abhängigkeitsarray.

useLayoutEffect(() => {
    // runs after the DOM update, before paint
    return () => {
        // cleanup, before the next run and on unmount
    };
}, [dependencies]);

Alles, was du von useEffect über Abhängigkeiten und Cleanup weißt, gilt. Der einzige Unterschied ist, wann React ihn aufruft.

Wann welcher läuft

Nach einem Render aktualisiert React das DOM und tut dann drei Dinge in fester Reihenfolge:

  1. Es führt alle Cleanups von Layout-Effekten und alle Layout-Effekte synchron aus.
  2. Es lässt den Browser zeichnen. Wenn ein Layout-Effekt State gesetzt hat, rendert React vorher erneut, immer noch vor dem Zeichnen.
  3. Es führt die normalen Effekte aus, meist nach dem Zeichnen.

Über einen ganzen Baum läuft jeder Layout-Effekt vor jedem normalen Effekt, und Kinder laufen vor ihren Eltern, weil ein Elternteil erst fertig sein kann, wenn seine Kinder an ihrem Platz sind:

Die Konsole zeigt zuerst beide Layout-Effekte, Child und dann Parent, dann beide normalen Effekte in derselben Reihenfolge. Das key={n} lässt jeden Klick ein frisches Parent mounten, also bekommst du jedes Mal dieselben vier Zeilen. Füge jedem Effekt ein Cleanup hinzu, das loggt, und beobachte, wie die alten vor den neuen laufen.

Das Problem mit dem Flackern

Stell dir einen Tooltip vor, der über seinem Button erscheinen soll, außer wenn darüber kein Platz ist, dann kommt er darunter. Ob Platz ist, weißt du erst, wenn der Tooltip im DOM ist und du seine Höhe messen kannst. Das Rendern passiert also in zwei Durchgängen:

  1. Den Tooltip irgendwo rendern, damit er gemessen werden kann.
  2. Ihn messen, die echte Position berechnen, State setzen, erneut rendern.

Mit useEffect kann der Browser zwischen diesen Durchgängen zeichnen, und man sieht den Tooltip von der ersten Position zur zweiten springen. Mit useLayoutEffect passiert der zweite Durchgang vor dem Zeichnen, also wird die erste Position nie gezeigt.

Fahre mit der Maus über den oberen Button: Darüber ist kein Platz, also erscheint der Tooltip darunter. Fahre über den unteren, und er erscheint darüber. Die Konsole sagt dir, welche Seite die Messung gewählt hat. Ändere useLayoutEffect zu useEffect und fahre erneut darüber: Der Tooltip kann jetzt für einen Frame oben in der Box aufblitzen, bevor er sich bewegt. Auf einem schnellen Rechner ist das oft zu schnell, um es zu bemerken, und genau deshalb wird dieser Bug ausgeliefert und dann von langsameren Handys gemeldet.

offsetHeight, offsetTop und getBoundingClientRect() zwingen den Browser alle, das Layout zu berechnen, also liefert ihr Lesen im Layout-Effekt die echten Werte des DOM, das React gerade geschrieben hat.

Ein Element messen

Dasselbe Muster funktioniert für jede Größe, von der der Render abhängt: mit einer Ref in einem Layout-Effekt lesen, im State speichern, damit rendern.

Klicke auf den Button, und die Zeile mit der Breite aktualisiert sich im selben Frame wie der Text. Die Seite zeigt nie einen neuen Text neben einer alten Breite, weil der zweite Render vor dem Zeichnen passiert. Entferne [text] aus dem Abhängigkeitsarray, und die Messung läuft nach jedem Render, auch nach dem, den ihr eigenes setWidth auslöst; sie stoppt dort nur, weil sich die Breite nicht mehr ändert.

Bevorzuge useEffect

Ein Layout-Effekt hält das Zeichnen auf, bis er zurückkehrt, und wenn er State setzt, auch bis der zusätzliche Render fertig ist. Ein langsamer lässt jedes Update auf der Seite langsam wirken. Nutze useLayoutEffect nur, wenn all das zutrifft:

  • Der Effekt liest Layout (Größe, Position, Scrollen).
  • Er ändert anhand dieser Messung, was gerendert wird.
  • Der Zwischenzustand wäre sichtbar und sähe falsch aus.

Alles andere (Daten laden, Abonnements, Timer, Logging, Analytics) gehört in useEffect. Eine schnelle Regel: Beginne mit useEffect und wechsle nur, wenn du ein Flackern siehst.

useEffect läuft meist nach dem Zeichnen, aber nicht immer. Wenn das Update von einer diskreten Aktion wie einem Klick oder einem Tastendruck kommt, kann React die Effekte ausführen, bevor der Browser zeichnet. Das ist ein Grund, warum ein Flackern in einer Situation auftritt und in einer anderen nicht.

Server-Rendering

Effekte laufen beim Server-Rendering nie, und useLayoutEffect auch nicht. Der Server schickt das HTML des ersten Renders, und der Layout-Effekt läuft erst nach der Hydration im Browser. React 18 und früher warnten mit „useLayoutEffect does nothing on the server“, wenn eine solche Komponente auf dem Server gerendert wurde; React 19 hat diese Warnung entfernt.

Wenn der erste auf dem Server gerenderte Frame ohne die Messung falsch aussähe, gibt es zwei übliche Lösungen: einen neutralen Fallback rendern (etwa den Tooltip verstecken, bis er gemessen ist) oder die Komponente nur auf dem Client rendern:

function ClientOnly({ children }) {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    return mounted ? children : null;
}

useInsertionEffect

Es gibt einen dritten Effekt-Hook, useInsertionEffect, der vor jedem Layout-Effekt läuft. Er existiert für CSS-in-JS-Bibliotheken, die zur Laufzeit <style>-Tags erzeugen: Fügen sie die Styles dort ein, sieht ein Layout-Effekt, der ein Element misst, es schon gestylt. Er kann keinen State aktualisieren, und Refs sind noch nicht angehängt, also nützt er in Anwendungscode nichts. Wenn du keine Styling-Bibliothek schreibst, brauchst du ihn nicht.

Häufig gestellte Fragen

Was ist der Unterschied zwischen useEffect und useLayoutEffect?

Der Zeitpunkt. useLayoutEffect läuft synchron, nachdem React das DOM geändert hat, aber bevor der Browser zeichnet, also werden seine State-Updates angewendet, bevor jemand etwas sieht. useEffect läuft meist nach dem Zeichnen. API und Abhängigkeitsarray sind gleich.

Wann sollte ich useLayoutEffect nutzen?

Wenn ein Effekt Layout liest (eine Größe oder eine Position) und anhand davon sofort ändert, was gerendert wird, etwa um einen Tooltip über oder unter seinem Button zu platzieren. Ohne ihn kann man für einen Frame die erste, falsche Position sehen.

Ist useLayoutEffect schlecht für die Performance?

Er hindert den Browser am Zeichnen, bis er fertig ist, samt jedem erneuten Rendern, das er auslöst. Ein langsamer Layout-Effekt lässt die ganze Seite langsam wirken, also nutze useEffect, solange du kein sichtbares Flackern vermeiden musst.

Läuft useLayoutEffect auf dem Server?

Nein. Beim Server-Rendering läuft keiner der beiden Effekte. Ältere React-Versionen gaben eine Warnung aus, wenn eine Komponente mit useLayoutEffect auf dem Server gerendert wurde; React 19 tut das nicht mehr, aber der Effekt läuft weiterhin nur im Browser.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

LOS GEHT'S