Kodokon kodokon.com

Memoization: memo, useMemo, useCallback

Lerne, wann React.memo, useMemo und useCallback deine App wirklich beschleunigen und wann sie sie umsonst verkomplizieren.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Wenn eine Komponente neu rendert, rendern standardmäßig alle ihre Kinder neu, ob sich ihre Props geändert haben oder nicht. Das ist keine Tragödie: Ein Rendern berührt das DOM nur, wenn sich die Ausgabe ändert. Memoization wird relevant, wenn häufige Renderings auf teure Teilbäume treffen. Drei Werkzeuge, drei Ziele: React.memo überspringt das Rendern einer Komponente, wenn ihre Props flach gleich sind, useMemo speichert einen berechneten Wert zwischen, useCallback speichert eine Funktion zwischen. Alle drei stützen sich auf Referenzgleichheit.

JSX
import { memo } from "react";

const Row = memo(function Row({ item, onSelect }) {
  return (
    <li onClick={() => onSelect(item.id)}>{item.label}</li>
  );
});
Row rendert nur neu, wenn item oder onSelect die Referenz ändert.

Die Kette ist nur so stark wie ihr schwächstes Glied. Wenn eine einzige Prop bei jedem Rendern eine frische Referenz erhält - eine Inline-Arrow-Funktion, ein Objekt- oder Array-Literal -, schlägt der Vergleich von memo fehl und die Komponente rendert trotzdem neu. useCallback stabilisiert die Referenz der Funktion, sodass der Vergleich gelingt.

JSX
import { useCallback } from "react";

function List({ items }) {
  const handleSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <ul>
      {items.map((item) => (
        <Row
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}
handleSelect behält bei jedem Rendern dieselbe Referenz.

useMemo hat zwei legitime Verwendungen: eine wirklich teure Berechnung zu vermeiden (Tausende Elemente sortieren oder aggregieren) und eine Referenz zu stabilisieren, die von einem memoisierten Kind konsumiert oder in den Abhängigkeiten eines Effekts aufgeführt wird. Bei einer trivialen Berechnung kostet der Cache mehr, als er spart: Abhängigkeitsvergleich, gebundener Speicher, schwerere Lesbarkeit.

JSX
import { useMemo } from "react";

function Stats({ orders }) {
  const total = useMemo(() => {
    return orders.reduce(
      (sum, order) => sum + order.amount,
      0
    );
  }, [orders]);

  return <p>Total: {total} €</p>;
}
Die Summe wird nur neu berechnet, wenn orders die Referenz ändert.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Ein Kind ist in React.memo eingehüllt, aber sein Elternteil übergibt ihm eine Inline-Arrow-Funktion als Prop. Ergebnis?
    • Memoization funktioniert: Funktionen werden über ihren Quellcode verglichen
    • Memoization ist wirkungslos: Die Inline-Funktion erzeugt bei jedem Rendern eine frische Referenz und der Vergleich schlägt fehl
    • React gibt in der Entwicklung eine Warnung aus
  2. Was garantiert useCallback(fn, [a]), solange sich a nicht ändert?
    • Dass die Funktion im Speicher nie neu erzeugt wird
    • Dass die Funktion nur einmal ausgeführt wird
    • Dass von einem Rendern zum nächsten dieselbe Funktionsreferenz zurückgegeben wird
  3. In welchem Fall ist useMemo klar gerechtfertigt?
    • Eine teure Berechnung oder eine zu stabilisierende Referenz für ein memoisiertes Kind oder eine Effekt-Abhängigkeit
    • Jeder abgeleitete Wert, vorsorglich
    • Daten über zwei Mounts der Komponente hinweg zwischenspeichern