Why Did You Render ohne Drittanbieter Tools debuggen
AI generated
{ }
React · Performance · Debugging
Unnoetige Re-Renders debuggen
ohne die Bibliothek why-did-you-render zu installieren

Nicht jede zusaetzliche npm-Abhaengigkeit ist noetig, um herauszufinden, warum eine Komponente zu oft neu rendert. Die Highlight-Updates-Option der React DevTools und ein zehn Zeilen langer eigener Hook reichen fuer die meisten Faelle bereits aus.

13 Min. Lesezeit React DevTools Custom Hooks Re-Render Debugging

1. Wozu ueberhaupt why-did-you-render und wann man es nicht braucht

Die Bibliothek why-did-you-render patcht React zur Laufzeit und protokolliert in der Konsole automatisch, wann und warum eine Komponente neu gerendert wird, inklusive eines Vergleichs der alten und neuen Props oder des alten und neuen State. Das ist praktisch fuer grosse Codebasen, in denen viele Komponenten gleichzeitig ueberwacht werden sollen, bringt aber eine zusaetzliche Abhaengigkeit, einen Monkeypatch von React selbst und einen gewissen Konfigurationsaufwand mit sich, der sich nicht fuer jede Debugging-Sitzung lohnt.

Fuer den haeufigeren Fall, dass man bereits eine konkrete Komponente im Verdacht hat, zu oft zu rendern, reichen zwei deutlich leichtgewichtigere Werkzeuge: die in den React DevTools eingebaute Highlight-Updates-Funktion fuer eine schnelle visuelle Kontrolle, und ein selbst geschriebener useWhyDidYouUpdate Hook fuer eine praezise Konsolenausgabe genau in der betroffenen Komponente, ganz ohne zusaetzliches Paket im package.json.

2. Highlight Updates in den React DevTools aktivieren

Im Components-Tab der React DevTools befindet sich ein Zahnrad-Symbol, das ein Einstellungsmenue oeffnet, in dem sich die Option 'Highlight updates when components render' aktivieren laesst. Ist sie aktiv, umrandet React DevTools jede Komponente kurz farbig, sobald sie neu rendert, waehrend man ganz normal mit der Anwendung interagiert, ganz ohne eine formale Profiler-Aufnahme starten zu muessen. Das macht sofort sichtbar, welche Bereiche der Seite bei einer bestimmten Interaktion aufleuchten, selbst wenn man noch gar nicht weiss, welche Komponente man eigentlich verdaechtigen sollte.

Die Farbe des Rahmens gibt dabei eine grobe Einschaetzung der Render-Haeufigkeit: Blaue Rahmen erscheinen bei seltenen Renders, gruene bis gelbe bei haeufigeren, und ein Bereich, der bei jedem einzelnen Tastendruck in einem entfernten Suchfeld aufblitzt, ist ein starkes Indiz fuer eine fehlende Memoisierung oder eine zu grob gefasste Context-Abhaengigkeit. Diese Methode liefert keine Zahlen und keine Ursachenanalyse, ist aber der schnellste erste Schritt, um ueberhaupt herauszufinden, wo man mit einer detaillierteren Untersuchung ansetzen sollte.

3. Wo Highlight Updates an seine Grenzen stoesst

Highlight Updates zeigt zuverlaessig das 'Dass' eines Renders, aber nicht das 'Warum': Man sieht, dass eine Komponente aufleuchtet, aber nicht, ob sich Props, State oder Context veraendert haben und welcher konkrete Wert dafuer verantwortlich war. Bei tief verschachtelten Komponentenbaeumen mit vielen gleichzeitig aufleuchtenden Bereichen wird es zudem schnell unuebersichtlich, welches Aufleuchten die eigentliche Ursache und welches nur eine Folge eines Renders weiter oben im Baum ist.

Fuer diese Faelle braucht es eine Methode, die direkt in der Konsole ausgibt, welche konkreten Werte sich zwischen zwei Renders geaendert haben. Genau diese Luecke fuellt ein kleiner selbst geschriebener Hook, der sich wie ein Debugging-Statement in genau die eine Komponente einbauen laesst, die man gerade untersucht, und danach wieder entfernt wird, ohne dass er jemals in Produktion landet.

4. Ein eigener useWhyDidYouUpdate Hook

Der Kerngedanke ist simpel: Man haelt die Props des vorherigen Renders in einer Ref, vergleicht sie beim naechsten Render mit den aktuellen Props und gibt ueber console.log genau die Eigenschaften aus, die sich unterscheiden. Ein useEffect, der nach jedem Render laeuft, ist dafuer ausreichend, weil er garantiert nach dem Rendern und nach dem Aktualisieren der Ref ausgefuehrt wird, sodass beim naechsten Durchlauf immer der Vergleich zum tatsaechlich vorherigen Zustand stattfindet.

Der Hook nimmt als Parameter den Namen der Komponente fuer die Konsolenausgabe und das komplette Props-Objekt entgegen und vergleicht jede einzelne Eigenschaft per Referenzgleichheit (===). Fuer primitive Werte wie Strings oder Zahlen ist das ausreichend, fuer Objekte und Arrays zeigt der Vergleich zuverlaessig genau das Problem, das ohnehin meist die eigentliche Ursache unnoetiger Renders ist: eine bei jedem Elternrender neu erzeugte Referenz, auch wenn der Inhalt unveraendert bleibt.


import { useEffect, useRef } from 'react';

function useWhyDidYouUpdate(name, props) {
  const previousProps = useRef();

  useEffect(() => {
    if (previousProps.current) {
      const allKeys = Object.keys({ ...previousProps.current, ...props });
      const changedProps = {};

      allKeys.forEach((key) => {
        if (previousProps.current[key] !== props[key]) {
          changedProps[key] = {
            from: previousProps.current[key],
            to: props[key],
          };
        }
      });

      if (Object.keys(changedProps).length) {
        console.log('[why-did-you-update]', name, changedProps);
      }
    }

    previousProps.current = props;
  });
}

function ExpensiveList(props) {
  useWhyDidYouUpdate('ExpensiveList', props);
  // ... eigentliche Render-Logik
}

5. Den Hook in der Praxis anwenden

Um den Hook einzusetzen, reicht ein einziger zusaetzlicher Aufruf am Anfang der verdaechtigen Komponente, direkt mit den bereits vorhandenen Props als zweitem Argument. Rendert die Komponente daraufhin unerwartet neu, erscheint in der Browser-Konsole eine Zeile, die exakt auflistet, welche Props sich vom vorherigen Wert zum neuen Wert veraendert haben, inklusive der alten und der neuen Referenz, was bei Funktionen und Objekten sofort zeigt, ob es sich um dieselbe Referenz oder eine neue handelt.

Besonders aufschlussreich ist der Fall, in dem die Konsole eine Eigenschaft als veraendert meldet, obwohl beide geloggten Werte inhaltlich identisch aussehen, etwa zwei Objekte mit denselben Feldwerten. Das ist der klassische Hinweis auf fehlende Memoisierung im Elternteil: Ein Objekt oder eine Callback-Funktion wird bei jedem Render der Elternkomponente neu erzeugt, obwohl sich der Inhalt nie aendert, und genau dieses Muster sollte man dann mit useMemo oder useCallback im Elternteil beheben, nicht in der Kindkomponente selbst.

6. Den Hook um State und Context erweitern

Die gezeigte Grundversion vergleicht ausschliesslich Props, aber unnoetige Renders koennen genauso gut durch internen State oder durch Context-Updates ausgeloest werden. Der Hook laesst sich problemlos erweitern, indem man ihm neben den Props zusaetzlich ein Objekt mit den relevanten State-Werten uebergibt, das intern genauso behandelt wird wie die Props, nur unter einem eigenen Namensraum in der Ausgabe, damit beide Quellen in der Konsole klar unterscheidbar bleiben.

Fuer Context-Updates ist die Erweiterung etwas subtiler, weil ein Context-Wert nicht als Prop hereinkommt, sondern per useContext direkt in der Komponente gelesen wird. Hier hilft es, den gelesenen Context-Wert einfach als zusaetzliches Feld in dasselbe Vergleichs-Objekt zu packen, das an den Hook uebergeben wird, sodass ein Context-bedingter Render genauso sichtbar wird wie ein durch Props ausgeloester.


function useWhyDidYouUpdate(name, values) {
  const previous = useRef();

  useEffect(() => {
    if (previous.current) {
      const changed = {};
      Object.keys(values).forEach((key) => {
        if (previous.current[key] !== values[key]) {
          changed[key] = { from: previous.current[key], to: values[key] };
        }
      });
      if (Object.keys(changed).length) {
        console.log('[why-did-you-update]', name, changed);
      }
    }
    previous.current = values;
  });
}

function CartWidget(props) {
  const [isOpen, setIsOpen] = useState(false);
  const theme = useContext(ThemeContext);

  useWhyDidYouUpdate('CartWidget', { ...props, isOpen, theme });
  // ... eigentliche Render-Logik
}

7. Alternative: React Scan und aehnliche moderne Tools

Neben why-did-you-render und den DevTools-eigenen Bordmitteln gibt es inzwischen auch moderne, sehr leichtgewichtige Alternativen wie react-scan, die aehnlich wie Highlight Updates funktionieren, aber mit deutlich weniger Konfiguration und ohne dass React selbst gepatcht werden muss. Solche Tools eignen sich gut fuer eine schnelle Uebersicht ueber eine ganze Seite, ersetzen aber nicht die praezise Analyse, die der eigene Hook fuer eine einzelne, bereits identifizierte Komponente liefert.

Fuer den in diesem Artikel beschriebenen Anwendungsfall, das gezielte Debuggen einer bereits verdaechtigten Komponente ohne zusaetzliche Abhaengigkeit, bleibt die Kombination aus Highlight Updates fuer die grobe Lokalisierung und dem eigenen Hook fuer die praezise Ursachenanalyse jedoch der pragmatischste Weg, weil beide Werkzeuge bereits vorhanden sind oder mit minimalem Aufwand selbst geschrieben werden koennen.

8. Den Hook wieder sauber entfernen

Da der Hook ausschliesslich fuer die Fehlersuche gedacht ist, sollte er nach Abschluss der Untersuchung wieder aus der Komponente entfernt werden, statt dauerhaft im Code zu verbleiben und bei jedem Render unnoetigen console.log-Output zu erzeugen. Wer den Hook haeufiger braucht, kann ihn in einer eigenen Utility-Datei ablegen, die ausschliesslich in Development Builds importiert wird, etwa ueber eine Bedingung auf process.env.NODE_ENV, damit er in Produktion garantiert keine Wirkung entfaltet.

Alternativ laesst sich der Hook so gestalten, dass er im Produktions-Build zu einer No-Op-Funktion wird, die effektiv nichts tut, sodass er theoretisch im Code verbleiben koennte, ohne Schaden anzurichten. In der Praxis ist es dennoch sauberer, das Debugging-Statement nach getaner Arbeit aktiv zu entfernen, damit der Code langfristig lesbar bleibt und niemand raetseln muss, warum eine Komponente einen ungewoehnlichen Hook-Aufruf enthaelt.

9. Vergleich der Debugging-Ansaetze

Alle drei vorgestellten Ansaetze, Highlight Updates, der eigene useWhyDidYouUpdate Hook und die Bibliothek why-did-you-render, haben ihre Berechtigung, je nachdem, wie gross die Codebasis ist und wie praezise die Ursachenanalyse sein muss. Fuer eine erste Orientierung ohne jede Vorbereitung ist Highlight Updates unschlagbar schnell, fuer eine gezielte, wiederholbare Untersuchung einer einzelnen Komponente ist der eigene Hook meist die richtige Wahl.

Die Bibliothek why-did-you-render lohnt sich vor allem dann, wenn dauerhaft und ueber viele Komponenten hinweg auf unnoetige Renders geachtet werden soll, etwa als Teil eines groesseren Performance-Audits, waehrend die beiden anderen Ansaetze fuer punktuelle, gezielte Debugging-Sessions gedacht sind und danach wieder aus dem Code verschwinden.

Ansatz Zusaetzliche Abhaengigkeit Zeigt Ursache Am besten fuer
Highlight Updates (DevTools) Nein Nein, nur dass gerendert wurde Schnelle visuelle Erstorientierung
useWhyDidYouUpdate (eigener Hook) Nein Ja, genaue Prop/State-Diffs Gezielte Untersuchung einer Komponente
react-scan Ja, aber leichtgewichtig Teilweise Ueberblick ueber ganze Seiten
why-did-you-render (Bibliothek) Ja, patcht React Ja, mit Diff-Ausgabe Dauerhaftes Monitoring in Entwicklung

Mironsoft

React-Architektur, Performance und Magento-Frontend-Integration

React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?

Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.

Performance-Audit

Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.

State-Architektur

Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.

Magento-Integration

GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.

10. Zusammenfassung

Re-Render Debugging ohne Drittanbieter: Das Wichtigste auf einen Blick

Highlight Updates

DevTools-Option, faerbt rendernde Komponenten waehrend normaler Nutzung ein, ohne formale Aufnahme.

useWhyDidYouUpdate

Kleiner eigener Hook mit useRef und useEffect, loggt exakt die veraenderten Props zwischen zwei Renders.

Referenzgleichheit

Der Hook vergleicht per ===, wodurch neu erzeugte Objekte und Funktionen als Ursache sichtbar werden.

Kein npm-Paket noetig

Beide Techniken kommen ohne zusaetzliche Abhaengigkeit oder React-Patch aus.

11. FAQ: Re-Render Debugging ohne Drittanbieter: Das Wichtigste auf einen Blick

1Brauche ich why-did-you-render ueberhaupt, wenn ich die DevTools nutze?
Fuer die meisten punktuellen Debugging-Faelle nicht. Die Highlight-Updates-Option der DevTools zeigt schnell, welche Komponenten rendern, und ein eigener useWhyDidYouUpdate Hook liefert die genaue Ursache, ohne dass React selbst gepatcht werden muss oder eine zusaetzliche Abhaengigkeit ins Projekt kommt.
2Wo aktiviere ich Highlight Updates in den React DevTools?
Im Components-Tab der DevTools befindet sich ein Zahnrad-Symbol, das ein Einstellungsmenue oeffnet. Dort laesst sich die Option Highlight updates when components render aktivieren, danach faerben sich rendernde Komponenten waehrend normaler Nutzung der Anwendung kurz farbig ein.
3Warum vergleicht der eigene Hook Props per Referenzgleichheit statt per tiefem Vergleich?
Referenzgleichheit deckt genau das haeufigste Problem auf: Objekte oder Funktionen, die bei jedem Render neu erzeugt werden, obwohl ihr Inhalt unveraendert bleibt. Ein tiefer Vergleich wuerde dieses Problem verschleiern, weil er inhaltlich gleiche Objekte als unveraendert einstufen wuerde, obwohl React selbst sie als unterschiedlich behandelt und deshalb neu rendert.
4Kann ich den Hook auch fuer State statt nur fuer Props verwenden?
Ja, der Hook ist bewusst generisch gehalten und nimmt ein beliebiges Objekt aus Werten entgegen. Man kann Props, State und ausgelesene Context-Werte in einem gemeinsamen Objekt zusammenfassen und dem Hook uebergeben, um alle drei Ursachen fuer einen Render an einer Stelle zu sehen.
5Verlangsamt der eigene Hook meine Anwendung spuerbar?
In Entwicklung nicht spuerbar, da der Vergleich nur ueber die Eigenschaften eines einzelnen Objekts laeuft und keine teure Berechnung darstellt. In Produktion sollte der Hook ohnehin nicht aktiv sein, entweder weil er entfernt wurde oder weil er ueber eine Bedingung auf process.env.NODE_ENV zu einer No-Op-Funktion wird.
6Was bedeutet es, wenn die Konsole eine Prop als veraendert meldet, obwohl der Inhalt gleich aussieht?
Das ist ein klares Signal fuer eine fehlende Memoisierung im Elternteil. Ein Objekt, Array oder eine Funktion wird bei jedem Render der Elternkomponente neu erzeugt und erhaelt dadurch eine neue Referenz, auch wenn die enthaltenen Werte identisch sind. Die Loesung ist useMemo oder useCallback im Elternteil, nicht in der betroffenen Kindkomponente.
7Ist react-scan eine sinnvolle Ergaenzung zu den DevTools-Bordmitteln?
Ja, fuer einen schnellen Ueberblick ueber eine ganze Seite mit vielen Komponenten kann react-scan praktischer sein als Highlight Updates, weil es teils zusaetzliche Aggregationen bietet. Fuer die gezielte Untersuchung einer einzelnen, bereits verdaechtigten Komponente bleibt der eigene Hook aber meist die praezisere Wahl.
8Sollte der useWhyDidYouUpdate Hook im Code bleiben oder wieder entfernt werden?
Nach Abschluss der Fehlersuche sollte der Hook aus der untersuchten Komponente wieder entfernt werden, um unnoetigen Konsolenoutput in normaler Entwicklungsarbeit zu vermeiden. Wird er dauerhaft gebraucht, gehoert er in eine eigene Utility-Datei mit einer expliziten Bedingung auf die Entwicklungsumgebung.
9Funktioniert der Hook auch mit Klassenkomponenten?
Nein, der gezeigte Hook nutzt useRef und useEffect und funktioniert deshalb nur in Function Components. Fuer Klassenkomponenten muesste dieselbe Logik manuell in componentDidUpdate nachgebaut werden, mit einem Instanzfeld anstelle der Ref, um die vorherigen Props zu speichern.
10Erkennt der Hook auch Renders, die durch das Elternteil ausgeloest werden, ohne dass sich Props aendern?
Nein, wenn sich weder Props noch die im Objekt uebergebenen State- oder Context-Werte aendern, aber die Komponente trotzdem rendert, liegt die Ursache meist beim Elternteil selbst, das ohne memo alle Kinder bei jedem eigenen Render neu rendert. In diesem Fall zeigt der Hook keine veraenderten Werte an, was selbst schon ein Hinweis auf fehlende Memoisierung der Kindkomponente per memo ist.