Menu

React.memo: Erneutes Rendern bei gleichen Props überspringen

memo umhüllt eine Komponente, damit React ihr erneutes Rendern überspringt, wenn ihre Props flach gleich denen vom letzten Mal sind. Lerne, wann es hilft, warum neue Objekte und Funktionen in Props es aushebeln und was es nicht aufhält.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Mit memo überspringt eine Komponente das erneute Rendern, wenn ihre Props dieselben wie beim letzten Mal sind. Pack eine Komponente in memo(...), und wenn ihr Elternteil rendert, vergleicht React zuerst jede neue Prop mit der alten; sind alle gleich, verwendet es das vorherige Ergebnis wieder, statt die Komponente erneut aufzurufen.

Tippe ins Input: Es kommt mit, und die Konsole zeigt render SlowList nur einmal, vom ersten Render. Entferne den Wrapper memo( (und seine schließende Klammer) und tippe erneut: Jeder Tastendruck wartet jetzt 150 ms auf die Liste und loggt einen Render.

Die Syntax

import { memo } from 'react';

const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
  • SomeComponent ist eine beliebige Funktionskomponente. Üblich ist, die Definition direkt zu umhüllen: const Card = memo(function Card(props) { ... }). Die innere Funktion zu benennen behält den Namen in den React DevTools und in Fehlermeldungen.
  • arePropsEqual ist optional und selten nötig (siehe unten).
  • Das Ergebnis ist eine Komponente, die du wie jede andere renderst.

Standardmäßig vergleicht React alte und neue Props flach: Es prüft jede Prop mit Object.is. Strings, Zahlen und Booleans werden nach Wert verglichen, also ist title="Hi" gleich title="Hi". Objekte, Arrays und Funktionen werden nach Referenz verglichen: Zwei Objekte mit demselben Inhalt sind trotzdem verschiedene Objekte.

Im Beispiel oben ist items außerhalb der Komponente deklariert, also ist es bei jedem Render dasselbe Array, und der Vergleich besteht.

Neue Objekte und Funktionen hebeln memo aus

Alles, was als Literal im Körper des Elternteils steht, wird bei jedem Render neu erzeugt: { name: 'Ada' }, [1, 2, 3], () => save(). Übergib so etwas an eine memo-Komponente, und die Prop ist nie gleich der vorherigen.

Jeder Klick loggt render Badge object prop, aber nie die Version mit String. Für den Fall mit Objekt gibt es drei Lösungen:

  • Primitive übergeben. name="Ada" statt user={{ name: 'Ada' }}, wenn das Kind nur ein oder zwei Felder braucht.
  • Konstanten aus der Komponente herausziehen. Ein Objekt, das sich nie ändert, kann auf Modulebene leben, wie items im ersten Beispiel.
  • Es cachen. Pack Objekte und Arrays, die aus Props oder State gebaut werden, in useMemo und Funktionen in useCallback.

children hat dasselbe Problem. <Card><p>Hello</p></Card> erzeugt bei jedem Render ein neues Element-Objekt <p>, also rendert eine memo-Card, die children bekommt, jedes Mal erneut.

Was memo nicht aufhält

memo schaut nur auf die Props, die vom Elternteil kommen. Eine memoisierte Komponente rendert trotzdem, wenn:

  • sich ihr eigener State ändert, über ihr eigenes useState oder useReducer;
  • sich ein Context ändert, den sie liest, über useContext.

Klicke auf Unrelated, und Panel wird übersprungen. Wechsle das Theme, und es rendert, weil es den Context liest. Klicke auf Like, und es rendert, weil sich sein eigener State geändert hat. Das ist das gewollte Verhalten: Bei memo geht es darum, Arbeit zu überspringen, wenn sich nichts geändert hat, wovon die Komponente abhängt, und Context und State sind Dinge, von denen sie abhängt.

Eine eigene Vergleichsfunktion

Das zweite Argument ersetzt den flachen Vergleich. Es erhält die vorherigen und die nächsten Props und gibt true zurück, wenn sie als gleich gelten sollen, also React den Render überspringt.

const MemoChart = memo(Chart, (prev, next) => {
    return (
        prev.points.length === next.points.length &&
        prev.points.every((p, i) => p === next.points[i]) &&
        prev.color === next.color &&
        prev.onSelect === next.onSelect
    );
});

Das brauchst du selten, und man macht es leicht falsch. Wenn die Funktion eine Prop ignoriert, zeigt die Komponente weiter den alten Wert dieser Prop. Eine Funktions-Prop zu ignorieren ist der schlimmste Fall: Die übersprungene Komponente ruft weiter die alte Funktion auf, die alten State liest. Vergleiche jede Prop und mach keine tiefen Gleichheitsprüfungen auf großen Daten, weil der Vergleich dann mehr kostet als der Render, den er spart. Stabile Props über useMemo und useCallback sind fast immer die bessere Lösung.

Prüfen, ob memo funktioniert

Die schnellste Prüfung ist die auf dieser Seite: ein console.log oben in der Komponente. Wenn es ausgibt, wo du ein Überspringen erwartet hast, logge auch die Props und vergleiche sie mit den vorherigen, um die zu finden, die sich geändert hat. In einem echten Projekt helfen die React DevTools weiter: Schalte in ihren Einstellungen „Highlight updates when components render“ ein, um zu sehen, welche Komponenten bei jeder Interaktion aufblitzen, und der Profiler-Tab zeichnet jeden Render auf und, mit der passenden Einstellung, warum jede Komponente gerendert hat.

Zwei Dinge sehen aus wie ein Versagen von memo, sind es aber nicht:

  • Der erste Render passiert immer. memo überspringt Renders erst, nachdem die Komponente einmal mit diesen Props gerendert hat.
  • Entwicklungs-Builds rendern womöglich öfter. Unter StrictMode rendert React in der Entwicklung jede Komponente zweimal, um unpuren Code zu finden. Die Vorschau hier läuft wie ein Produktions-Build, also bedeutet jede Log-Zeile oben einen echten Render.

Wann du memo nutzt

memo lohnt sich, wenn eine Komponente langsam rendert und ihr Elternteil oft mit denselben Props für sie rendert: eine lange Liste oder ein Diagramm neben einem Input, eine Seitenleiste in einer Seite, die sich jede Sekunde aktualisiert. Bei Komponenten, die schnell rendern oder deren Props sich ohnehin jedes Mal ändern, fügt es einen Vergleich hinzu und bringt nichts. Prüfe vor dem Memoisieren, ob du stattdessen umstrukturieren kannst, indem du State nach unten verschiebst oder JSX als children übergibst, wie auf der Seite zum Rendern gezeigt.

React Compiler 1.0 memoisiert, wenn dein Projekt ihn nutzt, Komponenten und ihre Props zur Build-Zeit automatisch, was die meisten manuellen memo-Aufrufe überflüssig macht. Ohne ihn funktioniert memo genau wie hier beschrieben und bleibt in React-Code üblich.

Häufig gestellte Fragen

Was macht React.memo?

Es gibt eine memoisierte Version einer Komponente zurück. Wenn das Elternteil rendert, vergleicht React die neuen Props mit den vorherigen und überspringt das Rendern der Komponente, wenn jede Prop per Object.is gleich ist.

Warum rendert meine memo-Komponente trotzdem neu?

Meist, weil eine Prop ein neues Objekt, Array oder eine neue Funktion ist, die beim Rendern des Elternteils erzeugt wurde, also nie gleich der vorherigen ist. Die Komponente rendert außerdem, wenn sich ihr eigener State ändert oder ein Context, den sie liest, mit oder ohne memo.

Was ist der Unterschied zwischen React.memo und useMemo?

memo umhüllt eine Komponente und überspringt ihren Render. useMemo ist ein Hook in einer Komponente, der einen berechneten Wert cacht. Oft arbeiten sie zusammen: useMemo hält eine Objekt-Prop stabil, damit memo überspringen kann.

Sollte ich jede Komponente in memo packen?

Nein. Die meisten Renders sind billig, und der Vergleich der Props hat eigene Kosten. Nutze es für Komponenten, die langsam rendern und oft dieselben Props bekommen.

Funktioniert memo mit children?

Selten. Als children übergebenes JSX ist bei jedem Render ein neues Objekt, also rendert eine memo-Komponente, die children bekommt, fast immer erneut.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

LOS GEHT'S