Handling State in React: Vier unveränderliche Ansätze zu berücksichtigen

Vielleicht der häufigste Punkt der Verwirrung in React heute: Zustand.

Stellen Sie sich vor, Sie haben ein Formular zum Bearbeiten eines Benutzers. Es ist üblich, einen einzelnen Änderungshandler zu erstellen, um Änderungen an allen Formularfeldern zu verarbeiten. Es könnte ungefähr so ​​aussehen:

updateState(event) { const {name, value} = event.target; let user = this.state.user; // this is a reference, not a copy... user[name] = value; // so this mutates state ? return this.setState({user}); }

Das Problem befindet sich in Zeile 4. Zeile 4 mutiert tatsächlich den Status, da die Benutzervariable eine Referenz auf den Status ist. Der Reaktionszustand sollte als unveränderlich behandelt werden.

Aus den React-Dokumenten:

Mutieren Sie niemals this.statedirekt, da ein späterer Aufruf setState()die von Ihnen vorgenommene Mutation ersetzen kann. Behandle, this.stateals ob es unveränderlich wäre.

Warum?

  1. setState-Stapel arbeiten hinter den Kulissen. Dies bedeutet, dass eine manuelle Statusmutation möglicherweise überschrieben wird, wenn setState verarbeitet wird.
  2. Wenn Sie eine shouldComponentUpdate-Methode deklarieren, können Sie keine ===-Gleichheitsprüfung verwenden, da sich die Objektreferenz nicht ändert . Der oben beschriebene Ansatz hat also auch potenzielle Auswirkungen auf die Leistung.

Fazit : Das obige Beispiel funktioniert oft in Ordnung, aber um Randfälle zu vermeiden, behandeln Sie den Zustand als unveränderlich.

Hier sind vier Möglichkeiten, den Staat als unveränderlich zu behandeln:

Ansatz 1: Object.assign

Object.assign erstellt eine Kopie eines Objekts. Der erste Parameter ist das Ziel. Anschließend geben Sie einen oder mehrere Parameter für Eigenschaften an, die Sie anheften möchten. Das Beheben des obigen Beispiels erfordert also eine einfache Änderung von Zeile 3:

updateState(event) { const {name, value} = event.target; let user = Object.assign({}, this.state.user); user[name] = value; return this.setState({user}); }

In Zeile 3 sage ich: "Erstellen Sie ein neues leeres Objekt und fügen Sie alle Eigenschaften von this.state.user hinzu." Dadurch wird eine separate Kopie des Benutzerobjekts erstellt, das im Status gespeichert ist. Jetzt kann ich das Benutzerobjekt in Zeile 4 sicher mutieren - es ist ein völlig getrenntes Objekt vom Objekt im Status.

Stellen Sie sicher, dass Sie Object.assign polyfill ausführen, da es im IE nicht unterstützt und nicht von Babel transpiliert wird. Vier Optionen zu berücksichtigen:

  1. Objekt zuweisen
  2. Die MDN-Dokumente
  3. Babel Polyfill
  4. Polyfill.io

Ansatz 2: Objektverteilung

Die Objektverbreitung ist derzeit eine Funktion der Stufe 3 und kann von Babel transpiliert werden. Dieser Ansatz ist prägnanter:

updateState(event) { const {name, value} = event.target; let user = {...this.state.user, [name]: value}; this.setState({user}); }

In Zeile 3 sage ich: "Verwenden Sie alle Eigenschaften in this.state.user, um ein neues Objekt zu erstellen, und setzen Sie dann die durch [name] dargestellte Eigenschaft auf einen neuen Wert, der an event.target.value übergeben wird." Dieser Ansatz funktioniert also ähnlich wie der Object.assign-Ansatz, hat jedoch zwei Vorteile:

  1. Keine Polyfüllung erforderlich, da Babel transpilieren kann
  2. Prägnanter

Sie können sogar Destrukturierung und Inlining verwenden, um dies zu einem Einzeiler zu machen:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); }

Ich destrukturiere ein Ereignis in der Methodensignatur, um einen Verweis auf event.target zu erhalten. Dann erkläre ich, dass state auf eine Kopie von this.state.user gesetzt werden soll, wobei die entsprechende Eigenschaft auf einen neuen Wert gesetzt wird. Mir gefällt, wie knapp das ist. Dies ist derzeit mein Lieblingsansatz beim Schreiben von Änderungshandlern. ?

Diese beiden oben genannten Ansätze sind die gebräuchlichsten und einfachsten Methoden, um mit unveränderlichen Zuständen umzugehen. Willst du mehr Leistung? Schauen Sie sich die beiden anderen Optionen unten an.

Ansatz 3: Unveränderlichkeitshelfer

Der Unveränderlichkeitshelfer ist eine praktische Bibliothek zum Mutieren einer Datenkopie, ohne die Quelle zu ändern. Diese Bibliothek wird in den Dokumenten von React vorgeschlagen.

// Import at the top: import update from 'immutability-helper'; updateState({target}) { let user = update(this.state.user, {$merge: {[target.name]: target.value}}); this.setState({user}); }

In Zeile 5 rufe ich merge auf, einen von vielen Befehlen, die von immutability-helper bereitgestellt werden. Ähnlich wie bei Object.assign übergebe ich das Zielobjekt als ersten Parameter und gebe dann die Eigenschaft an, in die ich zusammenführen möchte.

Unveränderlichkeitshelfer sind viel mehr als das. Es verwendet eine Syntax, die von der Abfragesprache von MongoDB inspiriert ist, und bietet eine Vielzahl leistungsstarker Möglichkeiten, mit unveränderlichen Daten zu arbeiten.

Ansatz 4: Immutable.js

Möchten Sie die Unveränderlichkeit programmatisch durchsetzen? Betrachten Sie unveränderlich.js. Diese Bibliothek bietet unveränderliche Datenstrukturen.

Hier ist ein Beispiel mit einer unveränderlichen Karte:

 // At top, import immutable import { Map } from 'immutable'; // Later, in constructor... this.state = { // Create an immutable map in state using immutable.js user: Map({ firstName: 'Cory', lastName: 'House'}) }; updateState({target}) { // this line returns a new user object assuming an immutable map is stored in state. let user = this.state.user.set(target.name, target.value); this.setState({user}); }

Es gibt drei grundlegende Schritte oben:

  1. Import unveränderlich.
  2. Setzen Sie den Status im Konstruktor auf eine unveränderliche Karte
  3. Verwenden Sie die set-Methode im Änderungshandler, um eine neue Kopie des Benutzers zu erstellen.

Die Schönheit von immutable.js: Wenn Sie versuchen, den Zustand direkt zu mutieren, schlägt dies fehl . Bei den anderen oben genannten Ansätzen ist es leicht zu vergessen, und React warnt Sie nicht, wenn Sie den Status direkt mutieren.

Die Nachteile von unveränderlich?

  1. Aufblähen . Immutable.js fügt Ihrem Bundle 57K minimiert hinzu. Wenn man bedenkt, dass Bibliotheken wie Preact React in nur 3K ersetzen können, ist das schwer zu akzeptieren.
  2. Syntax . Sie müssen Objekteigenschaften nicht direkt, sondern über Zeichenfolgen und Methodenaufrufe referenzieren. Ich bevorzuge user.name gegenüber user.get ('name').
  3. YATTL (noch etwas zu lernen) - Jeder, der Ihrem Team beitritt, muss noch eine weitere API zum Abrufen und Festlegen von Daten sowie einen neuen Satz von Datentypen lernen.

Einige andere interessante Alternativen:

  • nahtlos unveränderlich
  • Reagieren-Kopieren-Schreiben

Warnung: Achten Sie auf verschachtelte Objekte!

Option 1 und 2 oben (Object.assign und Object Spread) führen nur einen flachen Klon durch. Wenn Ihr Objekt verschachtelte Objekte enthält, werden diese verschachtelten Objekte als Referenz und nicht als Wert kopiert . Wenn Sie also das verschachtelte Objekt ändern, mutieren Sie das ursprüngliche Objekt. ?

Seien Sie chirurgisch, was Sie klonen. Klonen Sie nicht alle Dinge. Klonen Sie die Objekte, die sich geändert haben. Der Unveränderlichkeitshelfer (oben erwähnt) macht das einfach. Ebenso wie Alternativen wie immer, updeep oder eine lange Liste von Alternativen.

Sie könnten versucht sein, nach Deep-Merging-Tools wie Clone-Deep oder lodash.merge zu greifen, aber vermeiden Sie blind tiefes Klonen .

Hier ist der Grund:

  1. Tiefes Klonen ist teuer.
  2. Deep Cloning ist normalerweise verschwenderisch (stattdessen nur klonen, was sich tatsächlich geändert hat)
  3. Deep Cloning verursacht unnötiges Rendern, da React glaubt, dass sich alles geändert hat, obwohl sich möglicherweise nur ein bestimmtes untergeordnetes Objekt geändert hat.

Vielen Dank an Dan Abramov für die Vorschläge, die ich oben erwähnt habe:

Ich denke nicht, dass cloneDeep () eine gute Empfehlung ist. Es kann sehr teuer sein. Kopieren Sie nur die Teile, die sich tatsächlich geändert haben. Bibliotheken wie immutability-helper (//t.co/YadMmpiOO8), updeep (//t.co/P0MzD19hcD) oder immer (//t.co/VyRa6Cd4IP) helfen dabei.

- Dan Abramov (@dan_abramov), 23. April 2018

Letzter Tipp: Erwägen Sie die Verwendung von Functional setState

Eine andere Falte kann Sie beißen:

setState () mutiert this.state nicht sofort, sondern erstellt einen ausstehenden Statusübergang. Der Zugriff auf this.state nach dem Aufrufen dieser Methode kann möglicherweise den vorhandenen Wert zurückgeben.

Da setState-Aufrufe gestapelt sind, führt Code wie dieser zu einem Fehler:

updateState({target}) { this.setState({user: {...this.state.user, [target.name]: target.value}}); doSomething(this.state.user) // Uh oh, setState merely schedules a state change, so this.state.user may still have old value }

Wenn Sie Code ausführen möchten, nachdem ein setState-Aufruf abgeschlossen wurde, verwenden Sie das Rückrufformular von setState:

updateState({target}) { this.setState((prevState) => { const updatedUser = {...prevState.user, [target.name]: target.value}; // use previous value in state to build new state... return { user: updatedUser }; // And what I return here will be set as the new state }, () => this.doSomething(this.state.user); // Now I can safely utilize the new state I've created to call other funcs... ); }

Meine Einstellung

Ich bewundere die Einfachheit und das geringe Gewicht von Option 2 oben: Objektverteilung. Es ist keine Polyfüllung oder separate Bibliothek erforderlich, ich kann einen Änderungshandler in einer einzelnen Zeile deklarieren und mich chirurgisch über die Änderungen informieren. ? Arbeiten mit verschachtelten Objektstrukturen? Ich bevorzuge derzeit Immer.

Haben Sie andere Möglichkeiten, mit dem Status in React umzugehen? Bitte melden Sie sich über die Kommentare!

Suchen Sie mehr über Reagieren? ⚛

Ich habe mehrere React- und JavaScript-Kurse zu Pluralsight verfasst (kostenlose Testversion). Mein neuestes "Erstellen wiederverwendbarer Reaktionskomponenten" wurde gerade veröffentlicht! ?

Cory House is the author of multiple courses on JavaScript, React, clean code, .NET, and more on Pluralsight. He is principal consultant at reactjsconsulting.com, a Software Architect at VinSolutions, a Microsoft MVP, and trains software developers internationally on software practices like front-end development and clean coding. Cory tweets about JavaScript and front-end development on Twitter as @housecor.