Deutsch

Meistern Sie den React-Reconciliierungsprozess. Erfahren Sie, wie die korrekte Verwendung der 'key'-Eigenschaft die Listen-Rendering optimiert, Fehler verhindert und die Anwendungsleistung steigert.

Performance freisetzen: Ein Deep Dive in React Reconciliation Keys zur Listenoptimierung

In der Welt der modernen Webentwicklung ist die Erstellung dynamischer Benutzeroberflächen, die schnell auf Datenänderungen reagieren, von größter Bedeutung. React, mit seiner komponentenbasierten Architektur und deklarativen Natur, hat sich zu einem globalen Standard für den Aufbau dieser Oberflächen entwickelt. Im Herzen der Effizienz von React steht ein Prozess namens Reconciliation, der das Virtual DOM beinhaltet. Doch selbst die leistungsstärksten Werkzeuge können ineffizient eingesetzt werden, und ein häufiger Bereich, in dem Entwickler, sowohl neue als auch erfahrene, ins Straucheln geraten, ist das Rendern von Listen.

Wahrscheinlich haben Sie schon unzählige Male Code wie data.map(item => <div>{item.name}</div>) geschrieben. Es scheint einfach, fast trivial. Doch unter dieser Einfachheit verbirgt sich eine kritische Leistungsbetrachtung, die, wenn sie ignoriert wird, zu trägen Anwendungen und verwirrenden Fehlern führen kann. Die Lösung? Eine kleine, aber mächtige Eigenschaft: der key.

Dieser umfassende Leitfaden nimmt Sie mit auf einen Deep Dive in den React-Reconciliierungsprozess und die unentbehrliche Rolle von Keys beim Listen-Rendering. Wir werden nicht nur das 'Was', sondern auch das 'Warum' erforschen – warum Keys unerlässlich sind, wie man sie richtig auswählt und welche erheblichen Konsequenzen es hat, wenn man sie falsch macht. Am Ende verfügen Sie über das Wissen, um performantere, stabilere und professionellere React-Anwendungen zu schreiben.

Kapitel 1: React Reconciliation und das Virtual DOM verstehen

Bevor wir die Bedeutung von Keys würdigen können, müssen wir zunächst den grundlegenden Mechanismus verstehen, der React schnell macht: Reconciliation, unterstützt durch das Virtual DOM (VDOM).

Was ist das Virtual DOM?

Die direkte Interaktion mit dem Document Object Model (DOM) des Browsers ist rechenintensiv. Jedes Mal, wenn Sie etwas im DOM ändern – z. B. einen Knoten hinzufügen, Text aktualisieren oder einen Stil ändern –, muss der Browser eine beträchtliche Menge an Arbeit leisten. Er muss möglicherweise Stile und Layouts für die gesamte Seite neu berechnen, ein Prozess, der als Reflow und Repaint bezeichnet wird. In einer komplexen, datengesteuerten Anwendung können häufige direkte DOM-Manipulationen die Leistung schnell zum Erliegen bringen.

React führt eine Abstraktionsebene ein, um dies zu lösen: das Virtual DOM. Das VDOM ist eine leichte, im Speicher befindliche Darstellung des realen DOM. Stellen Sie es sich als einen Bauplan Ihrer UI vor. Wenn Sie React anweisen, die UI zu aktualisieren (z. B. durch Ändern des Zustands einer Komponente), berührt React nicht sofort das reale DOM. Stattdessen führt es die folgenden Schritte aus:

  1. Ein neuer VDOM-Baum, der den aktualisierten Zustand darstellt, wird erstellt.
  2. Dieser neue VDOM-Baum wird mit dem vorherigen VDOM-Baum verglichen. Dieser Vergleichsprozess wird als "Diffing" bezeichnet.
  3. React ermittelt die minimale Menge an Änderungen, die erforderlich sind, um das alte VDOM in das neue umzuwandeln.
  4. Diese minimalen Änderungen werden dann zusammengefasst und in einem einzigen, effizienten Vorgang auf das reale DOM angewendet.

Dieser Prozess, bekannt als Reconciliation, macht React so leistungsfähig. Anstatt das gesamte Haus neu zu bauen, handelt React wie ein erfahrener Bauunternehmer, der präzise ermittelt, welche spezifischen Ziegelsteine ersetzt werden müssen, wodurch die Arbeit und die Störungen minimiert werden.

Kapitel 2: Das Problem mit dem Rendern von Listen ohne Keys

Sehen wir uns nun an, wo dieses elegante System in Schwierigkeiten geraten kann. Stellen Sie sich eine einfache Komponente vor, die eine Liste von Benutzern rendert:


function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
}

Wenn diese Komponente zum ersten Mal gerendert wird, erstellt React einen VDOM-Baum. Wenn wir dem *Ende* des Arrays `users` einen neuen Benutzer hinzufügen, behandelt der Diffing-Algorithmus von React dies problemlos. Er vergleicht die alten und neuen Listen, sieht ein neues Element am Ende und fügt einfach ein neues `<li>` dem realen DOM an. Effizient und einfach.

Was passiert aber, wenn wir der Anfang der Liste einen neuen Benutzer hinzufügen oder die Elemente neu anordnen?

Nehmen wir an, unsere ursprüngliche Liste lautet:

Und nach einem Update wird sie zu:

Ohne eindeutige Kennungen vergleicht React die beiden Listen anhand ihrer Reihenfolge (Index). Folgendes wird angezeigt:

Das ist unglaublich ineffizient. Anstatt nur ein neues Element für "Charlie" am Anfang einzufügen, führte React zwei Mutationen und eine Einfügung durch. Bei einer großen Liste oder bei Listenelementen, die komplexe Komponenten mit eigenem Zustand sind, führt diese unnötige Arbeit zu einer erheblichen Leistungsminderung und, was noch wichtiger ist, zu potenziellen Fehlern im Komponentenstatus.

Aus diesem Grund zeigt die Entwicklerkonsole Ihres Browsers eine Warnung an, wenn Sie den obigen Code ausführen: "Warning: Each child in a list should have a unique 'key' prop." React sagt Ihnen explizit, dass es Hilfe benötigt, um seine Aufgabe effizient auszuführen.

Kapitel 3: Die `key`-Eigenschaft zur Rettung

Die key-Eigenschaft ist der Hinweis, den React benötigt. Es ist ein spezielles String-Attribut, das Sie beim Erstellen von Elementlisten angeben. Keys geben jedem Element eine stabile und eindeutige Identität über Re-Renders hinweg.

Lassen Sie uns unsere `UserList`-Komponente mit Keys umschreiben:


function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Hier gehen wir davon aus, dass jedes `user`-Objekt eine eindeutige `id`-Eigenschaft hat (z. B. aus einer Datenbank). Lassen Sie uns unser Szenario erneut besuchen.

Anfangsdaten:


[{ id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]

Aktualisierte Daten:


[{ id: 'u3', name: 'Charlie' }, { id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]

Mit Keys ist der Diffing-Prozess von React viel intelligenter:

  1. React betrachtet die Kinder des `<ul>` im neuen VDOM und überprüft ihre Keys. Es sieht `u3`, `u1` und `u2`.
  2. Es überprüft dann die Kinder des vorherigen VDOM und deren Keys. Es sieht `u1` und `u2`.
  3. React weiß, dass die Komponenten mit den Keys `u1` und `u2` bereits vorhanden sind. Es muss sie nicht verändern; es muss lediglich ihre entsprechenden DOM-Knoten an ihre neuen Positionen verschieben.
  4. React sieht, dass der Key `u3` neu ist. Es erstellt eine neue Komponente und einen DOM-Knoten für "Charlie" und fügt ihn am Anfang ein.

Das Ergebnis ist eine einzelne DOM-Einfügung und einige Neuordnungen, was weitaus effizienter ist als die mehreren Mutationen und Einfügungen, die wir zuvor gesehen haben. Die Keys bieten eine stabile Identität, sodass React Elemente über Renders hinweg verfolgen kann, unabhängig von ihrer Position im Array.

Kapitel 4: Den richtigen Key auswählen – Die goldenen Regeln

Die Wirksamkeit der `key`-Eigenschaft hängt vollständig von der Auswahl des richtigen Werts ab. Es gibt klare Best Practices und gefährliche Anti-Patterns, die zu beachten sind.

Der beste Key: Eindeutige und stabile IDs

Der ideale Key ist ein Wert, der ein Element innerhalb einer Liste eindeutig und dauerhaft identifiziert. Dies ist fast immer eine eindeutige ID aus Ihrer Datenquelle.

Hervorragende Quellen für Keys sind:


// GUT: Verwendung einer stabilen, eindeutigen ID aus den Daten.
<div>
  {products.map(product => (
    <ProductItem key={product.sku} product={product} />
  ))}
</div>

Das Anti-Pattern: Verwenden des Array-Index als Key

Ein häufiger Fehler ist die Verwendung des Array-Index als Key:


// SCHLECHT: Verwenden des Array-Index als Key.
<div>
  {items.map((item, index) => (
    <ListItem key={index} item={item} />
  ))}
</div>

Obwohl dies die React-Warnung zum Schweigen bringt, kann dies zu schwerwiegenden Problemen führen und gilt im Allgemeinen als Anti-Pattern. Die Verwendung des Index als Key teilt React mit, dass die Identität eines Elements an seine Position in der Liste gebunden ist. Dies ist im Wesentlichen dasselbe Problem wie überhaupt keinen Key zu haben, wenn die Liste neu geordnet, gefiltert oder Elemente vom Anfang oder der Mitte hinzugefügt/entfernt werden können.

Der State-Management-Bug:

Der gefährlichste Nebeneffekt der Verwendung von Index-Keys tritt auf, wenn Ihre Listenelemente ihren eigenen Zustand verwalten. Stellen Sie sich eine Liste von Eingabefeldern vor:


function UnstableList() {
  const [items, setItems] = React.useState([{ id: 1, text: 'First' }, { id: 2, text: 'Second' }]);

  const handleAddItemToTop = () => {
    setItems([{ id: 3, text: 'New Top' }, ...items]);
  };

  return (
    <div>
      <button onClick={handleAddItemToTop}>Add to Top</button>
      {items.map((item, index) => (
        <div key={index}>
          <label>{item.text}: </label>
          <input type="text" />
        </div>
      ))}
    </div>
  );
}

Versuchen Sie diese mentale Übung:

  1. Die Liste wird mit "First" und "Second" gerendert.
  2. Sie geben "Hello" in das erste Eingabefeld ein (das für "First").
  3. Sie klicken auf die Schaltfläche "Add to Top".

Was erwarten Sie zu geschehen? Sie würden erwarten, dass ein neues, leeres Eingabefeld für "New Top" erscheint und die Eingabe für "First" (die weiterhin "Hello" enthält) nach unten verschoben wird. Was tatsächlich passiert? Das Eingabefeld an der ersten Position (Index 0), das weiterhin "Hello" enthält, bleibt erhalten. Aber jetzt ist es dem neuen Datenelement "New Top" zugeordnet. Der Zustand der Eingabekomponente (ihr interner Wert) ist an ihre Position (key=0) gebunden, nicht an die Daten, die sie darstellen soll. Dies ist ein klassischer und verwirrender Bug, der durch Index-Keys verursacht wird.

Wenn Sie einfach `key={index}` in `key={item.id}` ändern, ist das Problem gelöst. React ordnet nun den Zustand der Komponente korrekt der stabilen ID der Daten zu.

Wann ist es akzeptabel, einen Index-Key zu verwenden?

Es gibt seltene Situationen, in denen die Verwendung des Index sicher ist, aber Sie müssen alle diese Bedingungen erfüllen:

  1. Die Liste ist statisch: Sie wird niemals neu geordnet, gefiltert oder Elemente von irgendwoher außer dem Ende hinzugefügt/entfernt.
  2. Die Elemente in der Liste haben keine stabilen IDs.
  3. Die für jedes Element gerenderten Komponenten sind einfach und haben keinen internen Zustand.

Selbst dann ist es oft besser, eine temporäre, aber stabile ID zu generieren, wenn dies möglich ist. Die Verwendung des Index sollte immer eine bewusste Wahl sein, kein Standard.

Der schlimmste Übeltäter: `Math.random()`

Verwenden Sie niemals `Math.random()` oder einen anderen nicht-deterministischen Wert für einen Key:


// SCHRECKLICH: Tun Sie dies nicht!
<div>
  {items.map(item => (
    <ListItem key={Math.random()} item={item} />
  ))}
</div>

Ein von `Math.random()` generierter Key ändert sich garantiert bei jedem einzelnen Render. Dies teilt React mit, dass die gesamte Komponentenliste aus dem vorherigen Render zerstört wurde und eine brandneue Liste mit völlig anderen Komponenten erstellt wurde. Dies zwingt React, alle alten Komponenten zu unmounten (ihren Zustand zu zerstören) und alle neuen zu mounten. Es macht den Zweck von Reconciliation zunichte und ist die schlechtestmögliche Option für die Leistung.

Kapitel 5: Erweiterte Konzepte und häufige Fragen

Keys und `React.Fragment`

Manchmal müssen Sie mehrere Elemente von einem `map`-Callback zurückgeben. Der Standardweg dazu ist mit `React.Fragment`. Wenn Sie dies tun, muss der `key` auf der `Fragment`-Komponente selbst platziert werden.


function Glossary({ terms }) {
  return (
    <dl>
      {terms.map(term => (
        // Der Key geht auf dem Fragment, nicht auf den Kindern.
        <React.Fragment key={term.id}>
          <dt>{term.name}</dt>
          <dd>{term.definition}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

Wichtig: Die Kurzschreibweise `<>...</>` unterstützt keine Keys. Wenn Ihre Liste Fragmente benötigt, müssen Sie die explizite Syntax `<React.Fragment>` verwenden.

Keys müssen nur unter Geschwistern eindeutig sein

Ein weit verbreitetes Missverständnis ist, dass Keys in Ihrer gesamten Anwendung global eindeutig sein müssen. Das stimmt nicht. Ein Key muss nur innerhalb seiner unmittelbaren Geschwisterliste eindeutig sein.


function CourseRoster({ courses }) {
  return (
    <div>
      {courses.map(course => (
        <div key={course.id}>  {/* Key für den Kurs */} 
          <h3>{course.title}</h3>
          <ul>
            {course.students.map(student => (
              // Dieser Studenten-Key muss nur innerhalb der Studentenliste dieses bestimmten Kurses eindeutig sein.
              <li key={student.id}>{student.name}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

Im obigen Beispiel könnten zwei verschiedene Kurse einen Studenten mit `id: 's1'` haben. Das ist vollkommen in Ordnung, da die Keys innerhalb verschiedener übergeordneter `<ul>`-Elemente ausgewertet werden.

Verwenden von Keys, um den Komponentenstatus absichtlich zurückzusetzen

Während Keys in erster Linie der Listenoptimierung dienen, dienen sie einem tieferen Zweck: Sie definieren die Identität einer Komponente. Wenn sich der Key einer Komponente ändert, versucht React nicht, die vorhandene Komponente zu aktualisieren. Stattdessen wird die alte Komponente (und alle ihre Kinder) zerstört und eine brandneue von Grund auf neu erstellt. Dadurch wird die alte Instanz unmounted und eine neue gemountet, wodurch der Zustand effektiv zurückgesetzt wird.

Dies kann eine leistungsstarke und deklarative Möglichkeit sein, eine Komponente zurückzusetzen. Stellen Sie sich beispielsweise eine `UserProfile`-Komponente vor, die Daten basierend auf einer `userId` abruft.


function App() {
  const [userId, setUserId] = React.useState('user-1');

  return (
    <div>
      <button onClick={() => setUserId('user-1')}>View User 1</button>
      <button onClick={() => setUserId('user-2')}>View User 2</button>
      
      <UserProfile key={userId} id={userId} />
    </div>
  );
}

Indem wir `key={userId}` auf die `UserProfile`-Komponente setzen, garantieren wir, dass jedes Mal, wenn sich die `userId` ändert, die gesamte `UserProfile`-Komponente verworfen und eine neue erstellt wird. Dies verhindert potenzielle Fehler, bei denen der Zustand aus dem Profil des vorherigen Benutzers (z. B. Formulardaten oder abgerufene Inhalte) verbleiben könnte. Es ist eine saubere und explizite Möglichkeit, die Komponentenidentität und den Lebenszyklus zu verwalten.

Fazit: Besseren React-Code schreiben

Die `key`-Eigenschaft ist weit mehr als nur ein Weg, um eine Konsolenwarnung zum Schweigen zu bringen. Sie ist eine grundlegende Anweisung an React und liefert die kritischen Informationen, die der Reconciliierungsalgorithmus benötigt, um effizient und korrekt zu arbeiten. Die Beherrschung der Verwendung von Keys ist ein Markenzeichen eines professionellen React-Entwicklers.

Fassen wir die wichtigsten Erkenntnisse zusammen:

Indem Sie diese Prinzipien verinnerlichen, schreiben Sie nicht nur schnellere, zuverlässigere React-Anwendungen, sondern gewinnen auch ein tieferes Verständnis der Kernmechanik der Bibliothek. Wenn Sie das nächste Mal über ein Array mappen, um eine Liste zu rendern, schenken Sie der `key`-Eigenschaft die Aufmerksamkeit, die sie verdient. Die Leistung Ihrer Anwendung – und Ihr zukünftiges Ich – werden Ihnen dafür danken.