Eine React-Komponente rendert erneut, wenn sich ihr eigener State ändert, wenn ihr Elternteil erneut rendert oder wenn sich ein Context ändert, den sie liest. Rendern heißt, dass React deine Komponentenfunktion erneut aufruft, um frisches JSX zu bekommen; danach aktualisiert es nur die Teile der Seite, die sich wirklich geändert haben.
Öffne die Konsole unter der Vorschau und klicke auf den Button. Alle drei Komponenten loggen, auch Title, das gar keine Props nimmt. Es hat gerendert, weil sein Elternteil gerendert hat.
Was einen Render auslöst
React rendert eine Komponente aus genau diesen Gründen:
- Der erste Render. Die App startet, oder die Komponente erscheint zum ersten Mal im Baum.
- Ihr State hat sich geändert. Du hast einen Setter aus
useStateoder ein Dispatch aususeReducermit einem neuen Wert aufgerufen. - Ihr Elternteil hat gerendert. Standardmäßig rendert, wenn eine Komponente rendert, jede Komponente mit, die sie zurückgibt, bis ganz nach unten.
- Ein Context, den sie liest, hat sich geändert. Eine Komponente, die
useContext(SomeContext)aufruft, rendert, wenn der nächste Provider einen neuenvalueübergibt (siehe useContext).
Ein verbreiteter Glaube ist, dass eine Komponente rendert, „weil sich ihre Props geändert haben“. Das dreht die Ursache um. Props sind Argumente, die das Elternteil beim Rendern übergibt, also können neue Props nur ankommen, wenn das Elternteil rendert. Und dass das Elternteil rendert, reicht schon allein: Title oben hat keine Props und rendert trotzdem jedes Mal.
State auf den Wert zu setzen, den er schon hat (verglichen mit Object.is), startet keinen Render der Kinder. React ruft diese eine Komponente vielleicht trotzdem einmal auf, bevor es bemerkt, dass sich nichts geändert hat, wirft das Ergebnis aber weg.
Render und Commit
Jedes Update durchläuft zwei Phasen:
- Render. React ruft deine Komponenten auf. Sie geben JSX zurück, also einfach Objekte, die beschreiben, was der Bildschirm zeigen soll. Auf der Seite ändert sich noch nichts, deshalb muss der Render pur sein: keine Schreibzugriffe aufs DOM, keine Anfragen, keine Änderungen an Variablen außerhalb der Komponente.
- Commit. React vergleicht die neue Ausgabe mit der vorherigen und wendet die Unterschiede auf das DOM an: Es fügt nur die Knoten ein, entfernt oder aktualisiert sie, die sich geändert haben. Dann zeichnet der Browser, und danach führt React deine Effekte aus.
Das Beispiel unten rendert bei jedem Klick, aber React behält dasselbe <input>-Element. Ein Effekt läuft nach jedem Commit und prüft das.
Tippe etwas ins Input und klicke ein paar Mal. Der getippte Text bleibt, weil React das Input nie ersetzt hat: Die einzige DOM-Änderung pro Klick ist die Zahl im <p>. Das Log zeigt auch die Reihenfolge, zuerst render und der Effekt nach dem Commit.
Das virtuelle DOM, ganz einfach
„Virtuelles DOM“ ist der populäre Name für die Objekte, die deine Komponenten zurückgeben. <p>Items: {count}</p> wird zu einem Aufruf kompiliert, der ein Objekt wie { type: 'p', props: { children: ['Items: ', 1] } } erzeugt. Nach einem Render geht React den neuen Baum aus diesen Objekten neben dem vorherigen durch, und wo Typ und Position übereinstimmen, behält es den bestehenden DOM-Knoten und aktualisiert nur geänderte Attribute und Texte. Dieser Vergleich heißt Reconciliation.
Der Begriff ist aus zwei Gründen ungenau. React hält keine zweite Kopie des DOM und vergleicht nicht zwei DOMs: Es vergleicht Element-Objekte mit seinem eigenen internen Baum aus Komponenten (dem Fiber-Baum). Und derselbe Prozess treibt Ziele an, die gar kein DOM haben, etwa React Native. Die React-Dokumentation meidet den Ausdruck meist und spricht stattdessen von Rendern und Committen. Was in der Praxis zählt, ist die Folge: Rendern ist im Vergleich zur Arbeit am DOM billig, weil die meisten Renders in einem kleinen DOM-Update oder gar keinem enden.
Aus der Art des Vergleichs folgen zwei Regeln. Ein anderer Elementtyp an derselben Stelle (ein <div>, ersetzt durch eine <section>, oder ComponentA durch ComponentB) zerstört den alten Teilbaum samt seinem State. Und in Listen sagt key React, welcher Eintrag welcher ist, damit es Knoten verschieben kann, statt sie neu zu bauen.
Unnötige Renders stoppen
Die meisten zusätzlichen Renders kosten nichts, was du bemerkst. Wenn einer doch schmerzt, etwa eine große Liste, die bei jedem Tastendruck woanders auf der Seite rendert, probiere diese Schritte der Reihe nach.
State nach unten verschieben
Wenn nur ein kleiner Teil des Bildschirms einen State-Wert nutzt, halte diesen State in einer Komponente, die nur diesen Teil umschließt. Diese Version rendert ProductList bei jedem Tastendruck neu:
export default function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ProductList />
</>
);
}
Verschiebe das Input und seinen State in eine eigene Komponente, und die Liste steckt nicht mehr in der Komponente, die rendert:
Tippe ein paar Buchstaben: Nur SearchBox loggt.
Stattdessen children übergeben
Manchmal muss der State in einem Wrapper um den teuren Teil leben, etwa in einem Panel, das eine Hervorhebung umschaltet. Lass den Wrapper children annehmen. Das Elternteil erzeugt die Kind-Elemente, und weil das Elternteil nicht rendert, wenn sich der State des Wrappers ändert, sind diese Elemente dieselben Objekte wie vorher, also überspringt React sie.
Klicke auf den Schalter, und nur Highlighter loggt. Verschiebe jetzt <Article /> anstelle von {children} in das JSX von Highlighter, und jeder Klick loggt auch render Article.
Memoisieren
Wenn keine der Umstrukturierungen passt, pack das Kind in memo. React vergleicht dann seine Props mit den vorherigen und überspringt den Render, wenn alle gleich sind. Beim Rendern erzeugte Objekte und Funktionen sind jedes Mal neu, also kommt memo für solche Props meist zusammen mit useMemo oder useCallback. Die Seite zu React.memo zeigt das mit laufenden Beispielen.
import { memo } from 'react';
const ProductList = memo(function ProductList({ category }) {
// skipped while category stays the same
});
Der React Compiler kann diese Art der Memoisierung zur Build-Zeit für dich hinzufügen, aber State umzustrukturieren ist trotzdem das Erste, was du probieren solltest: Es beseitigt die Arbeit, statt sie zu cachen.
Renders sind meist in Ordnung
Ein Render ist ein Funktionsaufruf, der Objekte zurückgibt. React führt Tausende davon schnell aus, und ein Render, der dieselbe Ausgabe erzeugt, ändert kein DOM. Füge memo nicht überall „zur Sicherheit“ hinzu: Jeder Vergleich hat eigene Kosten und macht Code schwerer lesbar. Miss zuerst. Der Profiler der React DevTools zeigt, welche Komponenten gerendert haben, warum und wie lange jede gebraucht hat.
Häufig gestellte Fragen
Was löst das erneute Rendern einer React-Komponente aus?
Drei Dinge: Ihr eigener State ändert sich, ihr Elternteil rendert, oder ein Context, den sie mit useContext liest, bekommt einen neuen Wert. Props sind kein eigener Auslöser: Neue Props kommen nur an, weil das Elternteil gerendert hat.
Rendert ein Kind erneut, wenn das Elternteil erneut rendert?
Ja, standardmäßig rendert jede Komponente in einem rendernden Elternteil mit, auch wenn sich ihre Props nicht geändert haben. Wenn du das Kind in memo packst, kann React es überspringen, sofern seine Props dieselben wie beim letzten Mal sind.
Was ist das virtuelle DOM in React?
Ein loser Name für den Baum aus einfachen JavaScript-Objekten (React-Elementen), den deine Komponenten zurückgeben. React vergleicht den neuen Baum mit dem vorherigen und ändert nur die echten DOM-Knoten, die sich unterscheiden.
Ist erneutes Rendern schlecht für die Performance?
Meistens nicht. Ein Render ist ein Funktionsaufruf, der Objekte erzeugt, und React fasst das DOM nur dort an, wo sich die Ausgabe geändert hat. Optimiere, wenn ein Render messbar langsam ist, zum Beispiel mit dem Profiler der React DevTools.
Was ist der Unterschied zwischen Render und Commit?
Rendern heißt, dass React deine Komponenten aufruft, um herauszufinden, wie der Bildschirm aussehen soll. Committen heißt, dass React die Unterschiede auf das DOM anwendet. Ein Render, der dieselbe Ausgabe erzeugt, committet keine DOM-Änderungen.