Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Fortgeschritten 13 Min Lesezeit

Stolpersteine: Struktur & Performance

Warum memo oft nicht hilft – und was stattdessen wirkt

In einem Satz

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.

Achtung Zuerst messen

Ö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

So nicht
const Kind = memo(Kinderliste);

<Kind
  items={daten.filter(Boolean)}     // neues Array
  style={{ padding: 8 }}            // neues Objekt
  onClick={() => waehle(id)}        // neue Funktion
/>
So geht's
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

Teuren Teilbaum vom Zähler entkoppeln
TeuerBaum rendert bei jedem Klick
function Seite() {
  const [n, setN] = useState(0);
  return (
    <div>
      <button onClick={() => setN(n + 1)}>{n}</button>
      <TeuerBaum />
    </div>
  );
}
TeuerBaum bleibt unangetastet
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

colocation.tsx
// ❌ 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

context.tsx
// 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}>
React.memo schützt nicht vor Context-Updates – der Wert kommt an den Props vorbei.

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

prioritaeten.txt
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
Tipp Reihenfolge der Werkzeuge
  1. Struktur ändern (State verschieben, children nutzen, 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

Teuren Teilbaum entkoppeln

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 TeuerBaum weiterhin bei einem Render
  • Es wird kein React.memo verwendet
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>
  );
}

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →