useLayoutEffect()
Der useLayoutEffect
Hook ist eine spezielle Variante von useEffect
in React, die synchron direkt nach allen DOM-Mutationen ausgeführt wird - noch bevor der Browser die Änderungen rendert. Dieser Hook ist essentiell für Operationen, die exakte DOM-Messungen erfordern oder visuelle Updates durchführen müssen, bevor der Benutzer Änderungen sehen kann. Im Gegensatz zum standardmäßigen useEffect
, der asynchron nach dem Rendering ausgeführt wird, blockiert useLayoutEffect
den Browser vom Aktualisieren des Bildschirms, bis die Callback-Funktion abgeschlossen ist. Diese Funktionalität macht ihn unverzichtbar für präzise DOM-Manipulationen, kann jedoch bei komplexen Berechnungen zu Leistungseinbußen führen.
Inhaltsverzeichnis
Einführung
Aufbau
Stellen wir uns vor, React baut eine Webseite wie ein Bühnenbild auf.
- Erste Phase: Der Render-Pass (
render
)- React nimmt die Components und deren aktuellen Zustand (Props und State).
- Es erstellt eine virtuelle Repräsentation davon, wie die Webseite aussehen soll (das Virtual DOM).
- Dann vergleicht es diese neue virtuelle Repräsentation mit der vorherigen und berechnet die minimal notwendigen Änderungen am echten DOM (also dem, was der Browser tatsächlich anzeigt).
- Zweite Phase: DOM-Mutationen
- React wendet diese berechneten Änderungen auf das echte DOM an. Elemente werden hinzugefügt, entfernt oder aktualisiert.
- Wichtig: Zu diesem Zeitpunkt hat der Browser diese Änderungen noch nicht auf dem Bildschirm dargestellt (gezeichnet). Die Bühne ist umgebaut, aber der Vorhang ist noch zu.
- Hier kommt der Unterschied:
useEffect
vs.useLayoutEffect
useEffect
(Standardfall)- Wann wird es ausgeführt? Nachdem React die Änderungen ins DOM geschrieben hat und nachdem Browser diese Änderungen auf dem Bildschirm gezeichnet hat.
- Wie wird es ausgeführt? Asynchron. Das bedeutet, es blockiert nicht den Browser. Der Nutzer sieht die gerenderte Seite und dann, kurz danach, läuft der Code in
useEffect
. - Vorteil: Die Seite bleibt responsive, da der Browser nicht auf das Ausführen des Effekts warten muss. Ideal für die meisten Seiteneffekte wie Daten laden, Event-Listener setzen etc.
useLayoutEffect
(Spezialfall)- Wann wird es ausgeführt? Nachdem React die Änderungen ins DOM geschrieben hat, aber bevor der Browser diese Änderungen auf dem Bildschirm gezeichnet hat.
- Wie wird es ausgeführt? Synchron. Das bedeutet, es blockiert den Browser. React wartet, bis der Code in
useLayoutEffect
komplett ausgeführt ist, bevor es dem Browser erlaubt, die Seite zu zeichnen. - Nachteil (und Grund zur Vorsicht): Wenn der
useLayoutEffect
Code lange braucht, verzögert es das erste Malen der Seite und kann zu einer spürbar langsameren Benutzererfahrung führen.
Warum braucht man useLayoutEffect
Die Haupt-Verwendung für useLayoutEffect
ist, wenn man das DOM liest (z.B. Dimensionen oder Positionen von Elementen messen) und dann synchron das DOM ändern möchte, bevor der Benutzer eine Zwischenstufe sieht, die vielleicht komisch aussieht (ein “Flickern”).
Als Beispiel kann man sich vorstellen, man möchte die Höhe eines Elements messen und basierend darauf die Position eines anderen Elements anpassen.
Beispiel 1 - Mit useEffect
In diesem Beispiel werden die Höhe eines Elements und die Position eines Tooltips verändern.
import { useState, useEffect, useRef } from 'react';
function ExampleWithUseEffect() {
const [contentHeight, setContentHeight] = useState(0);
const refContent = useRef(null);
// Simuliert dynamischen Inhalt
const [text, setText] = useState('Kurzer Text');
useEffect(() => {
setTimeout(() => {
setText('Ein viel längerer Text, der die Höhe verändert.')
}, 100);
}, []);
useEffect(() => {
// Läuft nachdem der Browser gezeichnet hat.
if (refContent.current) {
const newHeight = refContent.current.offsetHeight;
console.log('useEffect - Gemessene Höhe:', newHeight);
setContentHeight(newHeight);
}
}, [text]);
// Tooltip-Stil wird basierend auf contentHeight berechnet
const tooltipStyle = {
position: 'absolute',
bottom: `${contentHeight + 5}px`,
left: 20,
backgroundColor: 'lightyellow',
border: '1px solid orange',
padding: 5,
transition: 'bottom 0.1s ease-out'
};
return (
<div style={{
position: 'relative',
marginTop: 60,
padding: 20,
border: '1px solid lightblue'
}}>
<h3 style={{marginBottom: 40}}>Mit useEffect</h3>
<div
ref={refContent}
style={{
border: '1px solid green',
width: 200
}}
>
{text}
</div>
{contentHeight > 0 && (
<div style={tooltipStyle}>
Tooltip: (Höhe: {contentHeight})
</div>
)}
</div>
);
}
Als Ergebnis haben wir ein leicht springendes, flickerndes Verhalten, wenn wir die Seite mehrmals neualden.
Beispiel 1 - Mit useLayoutEffect
Im zweiten Teil schreiben wir unser Beispiel um, sodass wir diesen flickernden Effekt vermeiden.
import { useState, useEffect, useRef, useLayoutEffect } from 'react';
function ExampleWithUseLayoutEffect() {
const [contentHeight, setContentHeight] = useState(0);
const refContent = useRef(null);
// Simuliert dynamischen Inhalt
const [text, setText] = useState('Kurzer Text');
useEffect(() => {
setTimeout(() => {
setText('Ein viel längerer Text, der die Höhe verändert.');
}, 100);
}, []);
useLayoutEffect(() => {
// Läuft NACH den DOM-Manipulationen,
// ABER BEVOR der Browser zeichnet.
if (refContent.current) {
const newHeight = refContent.current.offsetHeight;
console.log('useLayoutEffect - Gemessene Höhe:', newHeight);
setContentHeight(newHeight);
}
}, [text]);
const tooltipStyle = {
position: 'absolute',
bottom: `${contentHeight + 5}px`,
left: 20,
backgroundColor: 'lightgoldenrodyellow',
border: '1px solid gold',
padding: 5
};
return (
<div style={{
position: 'relative',
marginTop: 60,
padding: 20,
border: '1px solid lightblue'
}}>
<h3 style={{marginBottom: 40}}>Mit useLayoutEffect</h3>
<div
ref={refContent}
style={{
width: 200,
border: '1px solid darkseagreen'
}}
>
{text}
</div>
{contentHeight > 0 && (
<div style={tooltipStyle}>
Tooltip (Höhe: {contentHeight})
</div>
)}
</div>
);
}
export default ExampleWithUseLayoutEffect;
Damit ist das Verhalten etwas anders. Hier gibt es kein Flickern beim Laden der Seite. Um es visuell deutlicher zu machen, habe ich beide Elemente nebeneinander gesetzt.