useMemo und useCallback in React: Performance gezielt optimieren

Wie du mit useMemo und useCallback unnötige Berechnungen und Re-Renders vermeidest – und wann du sie besser gar nicht einsetzt.

Teilen

Wenn deine React-Anwendung wächst, kommt irgendwann der Moment, in dem sich Interaktionen zäh anfühlen: Ein Klick reagiert verzögert, das Tippen in ein Suchfeld ruckelt. Häufig liegt das an teuren Berechnungen oder an Funktionen, die bei jedem Render neu erzeugt werden. Genau hier setzen die beiden Hooks useMemo und useCallback an. In diesem Beitrag schauen wir uns an, was sie wirklich tun, wann sie helfen – und wann du sie getrost weglassen kannst.

Warum Re-Renders teuer werden können

React rendert eine Komponente jedes Mal neu, wenn sich ihr State oder ihre Props ändern. Bei jedem Render läuft der gesamte Funktionskörper erneut durch. Das ist normalerweise kein Problem – React ist schnell. Kritisch wird es, wenn du bei jedem Render eine aufwendige Berechnung ausführst oder eine neue Funktion erzeugst, die dann als Prop an viele Kindkomponenten weitergegeben wird.

Ein einfaches Beispiel: Stell dir eine Liste vor, die bei jedem Tastendruck gefiltert und sortiert wird.

function Produktliste({ produkte }) {
  const [suche, setSuche] = useState("");

  // Läuft bei JEDEM Render – auch wenn sich nur "suche" ändert
  const gefiltert = produkte
    .filter((p) => p.name.includes(suche))
    .sort((a, b) => a.preis - b.preis);

  return (
    <div>
      <input value={suche} onChange={(e) => setSuche(e.target.value)} />
      <ul>
        {gefiltert.map((p) => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  );
}

Bei einer kurzen Liste ist das völlig in Ordnung. Bei zehntausend Einträgen und einer komplexen Sortierung kann die Berechnung aber spürbar Zeit kosten.

useMemo: Berechnungen zwischenspeichern

Mit useMemo merkt sich React das Ergebnis einer Berechnung und führt sie nur dann erneut aus, wenn sich eine der angegebenen Abhängigkeiten ändert. Das Prinzip nennt man Memoisierung.

import { useMemo, useState } from "react";

function Produktliste({ produkte }) {
  const [suche, setSuche] = useState("");

  const gefiltert = useMemo(() => {
    return produkte
      .filter((p) => p.name.includes(suche))
      .sort((a, b) => a.preis - b.preis);
  }, [produkte, suche]);

  return (
    <input value={suche} onChange={(e) => setSuche(e.target.value)} />
  );
}

Jetzt wird die Filter- und Sortierlogik nur neu ausgeführt, wenn sich produkte oder suche ändern. Ändert ein anderer State in der Komponente den Render, greift React auf das gespeicherte Ergebnis zurück. Wichtig: Das Abhängigkeits-Array muss alle Werte enthalten, die innerhalb der Funktion verwendet werden – sonst arbeitest du mit veralteten Daten.

useCallback: Funktionen stabil halten

In JavaScript ist jede Funktion ein eigenes Objekt. Definierst du eine Funktion im Komponentenkörper, entsteht bei jedem Render eine neue Referenz – auch wenn der Code identisch ist. Das ist normalerweise egal, wird aber relevant, wenn du die Funktion an eine mit React.memo optimierte Kindkomponente übergibst.

useCallback gibt dir dieselbe Funktionsreferenz zurück, solange sich die Abhängigkeiten nicht ändern:

import { useCallback, useState } from "react";

function Eltern() {
  const [zaehler, setZaehler] = useState(0);

  const erhoehen = useCallback(() => {
    setZaehler((z) => z + 1);
  }, []); // leeres Array => Funktion bleibt immer gleich

  return <Button onClick={erhoehen} label="Plus" />;
}

Gut zu wissen: useCallback(fn, deps) ist nur eine Kurzform für useMemo(() => fn, deps). Der eine Hook merkt sich also einen Wert, der andere eine Funktion.

Das Zusammenspiel mit React.memo

useCallback entfaltet seinen Nutzen erst zusammen mit React.memo. Dieses Wrapper-Funktion verhindert, dass eine Kindkomponente neu rendert, solange ihre Props referenzgleich bleiben.

import { memo } from "react";

const Button = memo(function Button({ onClick, label }) {
  console.log("Button rendert:", label);
  return <button onClick={onClick}>{label}</button>;
});

Ohne useCallback bekäme Button bei jedem Render der Eltern-Komponente eine neue onClick-Referenz und würde trotz memo jedes Mal neu rendern. Mit stabiler Referenz bleibt das erspart. Beide Bausteine gehören also zusammen: React.memo am Kind, useCallback (und ggf. useMemo für Objekt-Props) an der Übergabestelle.

Wann du sie NICHT brauchst

Ein verbreiteter Anfängerfehler ist, jede Funktion und jeden Wert vorsorglich zu memoisieren. Das ist meist kontraproduktiv, denn auch useMemo und useCallback haben Kosten: React muss die Abhängigkeiten vergleichen und die gespeicherten Werte im Speicher halten.

  • Günstige Berechnungen wie eine einfache Addition oder ein kurzer map-Aufruf profitieren nicht – der Vergleich kostet mehr als die Berechnung.
  • Werte, die nicht als Prop weitergereicht werden, brauchen selten eine stabile Referenz.
  • Erst messen, dann optimieren: Nutze den React Profiler oder die Performance-Tab der DevTools, um echte Engpässe zu finden, statt zu raten.

Als Faustregel gilt: Greife zu diesen Hooks, wenn du ein konkretes Performance-Problem beobachtest oder eine referenzstabile Prop für memo brauchst – nicht auf Verdacht.

Fazit

useMemo speichert das Ergebnis teurer Berechnungen, useCallback hält Funktionsreferenzen stabil. Beide verhindern unnötige Arbeit, wenn du sie gezielt einsetzt – idealerweise zusammen mit React.memo. Ihr größter Feind ist voreiliger Einsatz: Optimiere erst, wenn du ein echtes Problem gemessen hast. Verstehst du, warum React neu rendert, wählst du fast automatisch die richtige Stelle für diese Werkzeuge. Damit schreibst du React-Code, der nicht nur funktioniert, sondern auch bei wachsenden Datenmengen flüssig bleibt.