Zum Inhalt
wannacode

Objekte und Arrays aktualisieren

Kurz gesagt: Ein Objekt oder Array im React-Zustand wird mit einer neuen Kopie aktualisiert (Spread, map, filter), weil React Referenzen vergleicht und eine Änderung am alten nicht sieht.

Problem

„Entfernen“ entfernt nichts

Der Warenkorb hat drei Zeilen, jede mit einem Button „Entfernen“. Klick bei dem Brot auf „Entfernen“.

React

Die Konsole sagt Zeilen: 2. Der Bildschirm zeigt alle drei Zeilen und die alte Summe. Die Daten haben sich geändert, der Bildschirm folgt ihnen nicht, und es gibt keinen Fehler.

remove nimmt die Zeile mit splice heraus und ruft setCart(cart) auf. Der neue Wert wurde übergeben. Warum zeichnet React nicht?

Die Idee

Wie die Bibliothek eine Änderung sieht

Bei einer Zahl ist es klar: 1 und 2 sind verschieden. Ein Array und ein Objekt sind dagegen Referenzen: Die Variable zeigt auf ein Objekt im Speicher. Es gibt zwei Fragen. Zeigt die Variable auf ein anderes Objekt? Und hat sich der Inhalt desselben Objekts geändert? Jede Bibliothek fragt auf ihre Art, und hier zeigt sich der Unterschied im Code.

React stellt nur die erste Frage. Bei setCart(next) vergleicht es den alten und den neuen Zustand mit Object.is. Bei einem Array heißt das „ist es dasselbe Objekt“, nicht „hat es denselben Inhalt“.

splice ändert den Inhalt desselben Arrays. setCart(cart) übergibt dieselbe Referenz, Object.is sagt „dasselbe“, und React überspringt das Rendern. Deshalb ändert man in React das Array und die Objekte im Zustand nicht an Ort und Stelle. Du machst ein neues Array, und React sieht eine neue Referenz. Das heißt unveränderliches Aktualisieren (immutable update).

Sieh es dir an

Sieh: dasselbe Array oder ein neues

const [cart, setCart] = useState([bread]);
function addMilk() {
cart.push(milk);
setCart(cart);
}
function addCheese() {
setCart([...cart, cheese]);
}
Schritt für Schritt, in Worten
  1. cart zeigt auf ein Array mit einer Zeile. React hält diese Referenz als Zustand der Komponente, und der Bildschirm zeigt das Brot.
  2. cart.push(milk) fügt demselben Array eine Zeile hinzu. Der Inhalt hat sich geändert, aber es ist dasselbe Objekt.
  3. setCart(cart) übergibt dieselbe Referenz. React vergleicht die alte und die neue mit Object.is: dasselbe Objekt.
  4. Dasselbe heißt „nichts Neues“: React überspringt das Rendern. Die Milch steckt im Array, aber nicht auf dem Bildschirm.
  5. [...cart, cheese] macht ein neues Array: eine Kopie der Zeilen plus den Käse.
  6. setCart mit dem neuen Array: Object.is sagt „verschieden“, und React zeichnet neu.
  7. Jetzt zeigt der Bildschirm drei Zeilen. Auch die Milch, die bisher im alten Array versteckt war.
  8. Die Regel in React: Ändere das Array im Zustand nicht, gib ihm ein neues.
Die Milch steckt seit dem push im Array, erscheint auf dem Bildschirm aber erst zusammen mit dem Käse. Code mit einer Mutation scheitert nicht einfach. Er versteckt eine Änderung, die später herauskommt, bei einer ganz anderen Aktion.

Die Idee

Ein neues Array, ein neues Objekt

Der Warenkorb hat drei Aktionen: eine Zeile hinzufügen, die Menge einer Zeile ändern, eine Zeile entfernen.

// hinzufügen: ein neues Array mit den alten Zeilen und der neuen
setCart([...cart, { ...product, qty: 1 }]);
// eine Zeile ändern: ein neues Array, die geänderte Zeile ist ein neues Objekt
setCart(cart.map((line) => (line.id === id ? { ...line, qty: line.qty + 1 } : line)));
// entfernen: ein neues Array ohne die Zeile
setCart(cart.filter((line) => line.id !== id));
  • [...cart, x] ist ein neues Array: die alten Elemente und x am Ende.
  • { ...line, qty: 5 } ist ein neues Objekt: die Felder von line, mit geändertem qty.
  • map und filter geben immer ein neues Array zurück. Deshalb sind sie hier die wichtigsten Werkzeuge.

Probier es aus

Repariere „Entfernen“

Das ist der Warenkorb vom Anfang. Repariere remove so, dass die Zeile verschwindet und die Summe neu berechnet wird. Die Prüfung entfernt das Brot, dann den Käse.

React

Probier es aus

Finde den Fehler

„Entfernen“ entfernt nichts. Welche Zeile ist schuld?

Wähle die Zeile mit dem Fehler.

Die Idee

Eine Zeile des Arrays

Die Kundin drückt bei dem Brot auf „+“. Das qty eines Objekts muss sich ändern, und das Objekt steckt in einem Array.

cart.find(...).qty++ ändert das Objekt an Ort und Stelle: Es ist dasselbe Objekt, das React als alten Zustand hält. Das Array ist dasselbe, also zeichnet React nicht neu. Die Zahl geht nicht verloren: Sie erreicht den Bildschirm beim nächsten Rendern aus einem anderen Grund, und die Menge „springt“.

Vorsicht auch bei Kopien. [...cart] ist ein neues Array, aber die Zeilen darin sind dieselben Objekte. Das ist eine flache Kopie (shallow copy): Nur die erste Ebene wird kopiert. copy[0].qty++ ändert die Zeile auch im alten Zustand. Die geänderte Zeile muss ein neues Objekt sein: { ...line, qty: line.qty + 1 }. Die Zeilen, die sich nicht ändern, bleiben dieselben Objekte, und das ist in Ordnung.

Genauer, für Neugierige

Object.is. Wie ===, mit zwei Unterschieden: Object.is(NaN, NaN) ist true, und Object.is(0, -0) ist false. Bei Arrays und Objekten vergleichen beide Referenzen.

Verschachtelte Objekte. Jede Ebene auf dem Weg zur Änderung ist neu: { ...order, address: { ...order.address, city: 'Varna' } }. Die Ebenen daneben bleiben dieselben.

structuredClone(cart) macht eine tiefe Kopie: das Array und jedes Objekt darin. Das funktioniert, kopiert aber bei jeder Änderung alles, und Funktionen kann es nicht kopieren. Für eine Zeile sind map und ... billiger.

Immer. Die Bibliothek Immer lässt dich schreiben, als würdest du mutieren (draft[0].qty++), und macht die neuen Objekte für dich. Sie ist nicht in der Sandbox.

Es funktioniert auch in Vue. Das neue Array ist auch in Vue richtig: cart.value = cart.value.filter(...). Umgekehrt gilt es nicht: Die Mutation, die in Vue normal ist, wird in React nicht gesehen.

Probier es aus

„+“ und „−“

Schreibe more und less. „+“ erhöht die Menge der Zeile um 1, und „−“ verringert sie um 1. Ist die Menge 1, entfernt „−“ die Zeile. Die Summe folgt der Menge.

React

Übung

Katalog und Warenkorb

Der Katalog hat vier Produkte mit einem Button „Hinzufügen“ (der Name für den Screenreader: „Hinzufügen Weißbrot“). Schreibe add und remove. Der erste Klick auf „Hinzufügen“ macht eine Zeile mit der Menge 1, und die nächsten erhöhen die Menge derselben Zeile, statt eine neue zu machen. „Entfernen“ entfernt die Zeile. Ist der Warenkorb leer, steht dort „Der Warenkorb ist leer“.

React

Test

Teste dich

1. cart.push(line); setCart(cart); Warum ändert sich der Bildschirm nicht?
2. Welche Zeile erhöht die Menge einer Zeile richtig?
3. const next = [...cart]; next[0].qty++; setCart(next); Was ist falsch?
4. Wie entfernst du eine Zeile, ohne das Array im Zustand zu ändern?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·