Stolpersteine: Struktur & Performance
Warum memo oft nicht hilft – und was stattdessen wirkt
Die meisten Geschwindigkeitsprobleme in React löst man durch Umbauen, nicht durch Memoisieren. Und die meisten „Probleme“ sind gar keine.
Bevor du irgendetwas optimierst: Die allermeisten React-Apps sind schnell genug. Und wenn nicht, liegt es überraschend selten am Zeichnen.
Öffne den ProfilerEin Messwerkzeug in den React DevTools, das zeigt, welche Komponente wie lange gebraucht hat.→ in den React DevTools und nimm die Interaktion auf. Er zeigt dir, welche Komponente wie lange gebraucht hat und warum sie überhaupt neu gezeichnet wurde. Ohne Messung optimierst du Vermutungen.
1. memo hilft nicht, wenn die Props immer neu sind
const Kind = memo(Kinderliste);
<Kind
items={daten.filter(Boolean)} // neues Array
style={{ padding: 8 }} // neues Objekt
onClick={() => waehle(id)} // neue Funktion
/> const style = { padding: 8 }; // außerhalb der Komponente
const sichtbar = useMemo(() => daten.filter(Boolean), [daten]);
const handleClick = useCallback(() => waehle(id), [id]);
<Kind items={sichtbar} style={style} onClick={handleClick} /> memo vergleicht flach. Eine einzige instabile Prop genügt, und der Vergleich schlägt immer fehl – die Optimierung kostet dann nur Zeit.
2. Der children-Trick
function Seite() {
const [n, setN] = useState(0);
return (
<div>
<button onClick={() => setN(n + 1)}>{n}</button>
<TeuerBaum />
</div>
);
} function Seite({ children }) {
const [n, setN] = useState(0);
return (
<div>
<button onClick={() => setN(n + 1)}>{n}</button>
{children}
</div>
);
}
// Elternteil:
<Seite><TeuerBaum /></Seite> children wird im Elternteil erzeugt. Rendert Seite neu, ist das children-Element dasselbe Objekt wie vorher – React überspringt den Teilbaum ganz ohne memo.
3. State so tief wie möglich
// ❌ Der Suchtext lebt in App -> jeder Tastendruck rendert alles
function App() {
const [q, setQ] = useState("");
return (
<>
<Kopf />
<Suche q={q} setQ={setQ} />
<Riesenliste />
<Fuss />
</>
);
}
// ✅ Der Text lebt dort, wo er gebraucht wird
function Suche() {
const [q, setQ] = useState("");
return <input value={q} onChange={(e) => setQ(e.target.value)} />;
} 4. Context als Re-Render-Kanone
// Ein Context mit allem drin -> jede Änderung rendert alle Verbraucher
<AppContext value={{ user, theme, warenkorb, benachrichtigungen }}>
// Besser: nach Änderungsfrequenz aufteilen
<UserContext value={user}>
<ThemeContext value={theme}>
<KorbContext value={korb}> 5. Große Listen
Ab ein paar hundert sichtbaren Zeilen hilft kein memo mehr – dann ist das DOM selbst das Problem. Die Antwort heißt Virtualisierung (@tanstack/react-virtual, react-window): nur die sichtbaren Zeilen existieren wirklich.
6. Was wirklich langsam ist
Häufige echte Ursachen – ungefähr in dieser Reihenfolge:
1. Zu große JS-Bundles -> Code-Splitting, dynamische Imports
2. Bilder ohne Größe/Format -> next/image, moderne Formate
3. Wasserfall-Requests -> parallel laden, auf dem Server holen
4. Riesige Listen im DOM -> virtualisieren
5. Teure Berechnungen -> useMemo, Web Worker
Selten die Ursache:
• "die Komponente rendert zweimal"
• eine fehlende memo-Hülle - Struktur ändern (State verschieben,
childrennutzen, aufteilen). 2. Rendern priorisieren (useTransition). 3. Erst dann memoisieren. 4. Und irgendwann übernimmt das der React Compiler ohnehin.
Sitzt das schon?
5 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.
Jetzt selbst schreiben
TeuerBaum zählt mit, wie oft es gerendert wurde. Aktuell rendert es bei jedem Klick auf den Zähler mit. Bau die Struktur so um, dass der Zähler den Teilbaum nicht mehr anfasst – ohne memo zu benutzen.
- □Der Zähler funktioniert weiterhin
- □Nach drei Klicks steht
TeuerBaumweiterhin bei einem Render - □Es wird kein
React.memoverwendet
import { useRef, useState } from "react"; function TeuerBaum() { const renders = useRef(0); renders.current += 1; return <p data-testid="teuer">Renders: {renders.current}</p>; } export default function App() { const [n, setN] = useState(0); // TODO: so umbauen, dass TeuerBaum vom Zähler unberührt bleibt return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <button onClick={() => setN((v) => v + 1)}>Zähler: {n}</button> <TeuerBaum /> </div> ); }