Warum kann ich den Status einer Komponente nicht direkt ändern?

78

Ich verstehe, dass React-Tutorials und -Dokumentationen ohne Zweifel warnen, dass der Status nicht direkt mutiert werden sollte und dass alles durchlaufen sollte setState.

Ich möchte verstehen, warum genau ich nicht einfach den Status direkt ändern und dann (in derselben Funktion) aufrufen kann, this.setState({})nur um den auszulösen render.

Beispiel: Der folgende Code scheint gut zu funktionieren:

const React = require('react');

const App = React.createClass({
    getInitialState: function() {
        return {
            some: {
                rather: {
                    deeply: {
                        embedded: {
                            stuff: 1
                        }}}}};
    },
    updateCounter: function () {
        this.state.some.rather.deeply.embedded.stuff++;
        this.setState({}); // just to trigger the render ...
    },
    render: function() {
        return (
                <div>
                Counter value: {this.state.some.rather.deeply.embedded.stuff}
                <br></br>
                <button onClick={this.updateCounter}>inc</button>
                </div>
        );
    }
});

export default App;

Ich bin alle dafür, Konventionen zu befolgen, aber ich möchte mein weiteres Verständnis dafür verbessern, wie ReactJS tatsächlich funktioniert und was schief gehen kann oder ob es mit dem obigen Code nicht optimal ist.

Die Hinweise in der this.setStateDokumentation identifizieren grundsätzlich zwei Fallstricke:

  1. Wenn Sie den Status direkt mutieren und anschließend aufrufen, this.setStatekann dies die von Ihnen vorgenommene Mutation ersetzen (überschreiben?). Ich sehe nicht, wie dies im obigen Code passieren kann.
  2. Dies setStatekann this.stateauf asynchrone / verzögerte Weise effektiv mutieren. Wenn Sie also this.statedirekt nach dem Anruf darauf zugreifen , können this.setStateSie nicht garantiert auf den endgültigen mutierten Status zugreifen. Ich verstehe, dass dies kein Problem ist, wenn this.setStateder letzte Aufruf der Update-Funktion ist.
Marcus Junius Brutus
quelle
2
Überprüfen Sie die Hinweise unter setStateDokumentation . Es behandelt einige der guten Gründe.
Yuya
@ Kujira Ich habe meine Frage aktualisiert, um auch die von Ihnen erwähnten Notizen zu beantworten.
Marcus Junius Brutus
4
Neben der Tatsache, dass Sie glauben, dass Sie es steuern können, schließen Sie einfach den Workflow eines Frameworks kurz. Mit Javascript können Sie dies tun. Denken Sie jedoch daran, dass das Framework nicht mehr für die Konsistenz Ihres Staates verantwortlich ist, sobald Sie das Muster gebrochen haben.
pietro909
4
Es geht nicht darum, den Zustand nicht direkt zu mutieren, es geht darum, nicht zu mutieren.
Wikipedia
Seltsam, dies wurde vor 4 Monaten gestellt und immer noch nicht akzeptiert. Ist diese Frage so schwer zu beantworten? Ich kann nicht wirklich eine Antwort auf diese
Frage

Antworten:

47

Die React-Dokumente fürsetState haben Folgendes zu sagen:

NIEMALSthis.state direkt mutieren , da ein späterer Aufruf setState()die von Ihnen vorgenommene Mutation ersetzen kann. Behandle, this.stateals ob es unveränderlich wäre.

setState()mutiert nicht sofort, this.statesondern erzeugt einen ausstehenden Zustandsübergang. Der Zugriff this.statenach dem Aufrufen dieser Methode kann möglicherweise den vorhandenen Wert zurückgeben.

Es gibt keine Garantie für den synchronen Betrieb von Anrufen an setStateund Anrufe können für Leistungssteigerungen gestapelt werden.

setState()löst immer ein erneutes Rendern aus, es sei denn, die bedingte Renderlogik ist in implementiert shouldComponentUpdate(). Wenn veränderbare Objekte verwendet werden und die Logik nicht implementiert werden kann shouldComponentUpdate(), setState()werden unnötige Neurender vermieden , wenn nur aufgerufen wird , wenn der neue Status vom vorherigen Status abweicht.

Wenn Sie this.statedirekt ändern , erstellen Sie grundsätzlich eine Situation, in der diese Änderungen möglicherweise überschrieben werden.

Bezogen auf Ihre erweiterten Fragen 1) und 2) setState()ist nicht unmittelbar. Es stellt einen Statusübergang in die Warteschlange, der auf dem basiert, was seiner Meinung nach vor sich geht und möglicherweise nicht die direkten Änderungen an enthältthis.state . Da es nicht sofort angewendet, sondern in die Warteschlange gestellt wird, ist es durchaus möglich, dass dazwischen etwas geändert wird, sodass Ihre direkten Änderungen überschrieben werden.

Wenn nichts anderes, sind Sie vielleicht besser dran, wenn Sie nur bedenken, dass nicht direktes Ändern this.stateals gute Praxis angesehen werden kann. Möglicherweise wissen Sie persönlich, dass Ihr Code mit React so interagiert, dass diese Überschreibungen oder andere Probleme nicht auftreten können, aber Sie schaffen eine Situation, in der andere Entwickler oder zukünftige Updates plötzlich mit seltsamen oder subtilen Problemen konfrontiert werden.

Ouroborus
quelle
"Es stellt einen Statusübergang in die Warteschlange, basierend auf dem, was seiner Meinung nach vor sich geht, der möglicherweise nicht die direkten Änderungen an this.state enthält. Da er in die Warteschlange gestellt und nicht sofort angewendet wird, ist es durchaus möglich, dass dazwischen etwas geändert wird, sodass Ihre direkten Änderungen überschrieben werden . " Könnten Sie bitte näher erläutern, was Sie damit meinen? Geben Sie ein Beispiel, wo dies ein Problem ist?
Jhegedus
1
@jhegedus Tauchen Sie in die Codebasis von React ein: Die Behandlung von Statusänderungen geht äußerst detailliert auf die Funktionsweise der setState()Systeme und des Rückrufsystems ein und gibt Ihnen eine gute Vorstellung davon, warum es ein Problem geben würde.
Ouroborus
Danke für den Tipp Ouroborus!
Jhegedus
Aus diesem Grund positiv bewertet: "Es stellt einen Zustandsübergang in die Warteschlange, basierend auf dem, was seiner Meinung nach vor sich geht, der möglicherweise nicht die direkten Änderungen an diesem Zustand enthält."
Marcus Junius Brutus
56

Diese Antwort soll genügend Informationen liefern, um den Status nicht direkt in React zu ändern / zu mutieren.

Die Reaktion folgt dem unidirektionalen Datenfluss . Das heißt, der Datenfluss innerhalb der Reaktion sollte und wird voraussichtlich auf einer Kreisbahn liegen.

Datenfluss der Reaktion ohne Fluss

Geben Sie hier die Bildbeschreibung ein

Damit React so funktioniert, haben Entwickler das React der funktionalen Programmierung ähnlich gemacht . Die Faustregel der funktionalen Programmierung ist Unveränderlichkeit . Lassen Sie es mich klar und deutlich erklären.

Wie funktioniert der unidirektionale Fluss?

  • states sind ein Datenspeicher, der die Daten einer Komponente enthält.
  • Das viewRendern einer Komponente basiert auf dem Status.
  • Wenn viewetwas auf dem Bildschirm geändert werden muss, sollte dieser Wert vom bereitgestellt werden store.
  • Um dies zu erreichen, bietet React eine setState()Funktion, die einen objectneuen Status aufnimmt statesund object.assign()den vorherigen Status vergleicht und zusammenführt (ähnlich ) und den neuen Status zum Statusdatenspeicher hinzufügt.
  • Wenn sich die Daten im Statusspeicher ändern, wird durch Reagieren ein erneutes Rendern mit dem neuen Status ausgelöst, den der viewBenutzer verbraucht, und auf dem Bildschirm angezeigt.

Dieser Zyklus wird während der gesamten Lebensdauer der Komponente fortgesetzt.

Wenn Sie die obigen Schritte sehen, zeigt dies deutlich, dass beim Ändern des Status viele Dinge dahinter stehen. Wenn Sie also den Status direkt mutieren und setState()mit einem leeren Objekt aufrufen . Das previous statewird mit Ihrer Mutation verschmutzt. Aufgrund dessen wird der flache Vergleich und die Zusammenführung von zwei Zuständen gestört oder wird nicht stattfinden, da Sie jetzt nur einen Zustand haben. Dies stört alle Lebenszyklusmethoden der Reaktion.

Infolgedessen verhält sich Ihre App abnormal oder stürzt sogar ab. In den meisten Fällen hat dies keine Auswirkungen auf Ihre App, da alle Apps, die wir zum Testen verwenden, ziemlich klein sind.

Ein weiterer Nachteil der Mutation von Objectsund Arraysin JavaScript besteht darin, dass Sie beim Zuweisen eines Objekts oder eines Arrays nur eine Referenz auf dieses Objekt oder dieses Array erstellen. Wenn Sie sie mutieren, sind alle Verweise auf dieses Objekt oder dieses Array betroffen. React behandelt dies auf intelligente Weise im Hintergrund und gibt uns einfach eine API, damit es funktioniert.

Die häufigsten Fehler bei der Behandlung von Zuständen in React

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// bad approach
const b = this.state.a.push(6)
this.setState({
  a: b
}) 

Im obigen Beispiel this.state.a.push(6)wird der Status direkt mutiert. Das Zuweisen zu einer anderen Variablen und das Aufrufen entspricht setStatedem unten gezeigten. Da wir den Status ohnehin mutiert haben, macht es keinen Sinn, ihn einer anderen Variablen zuzuweisen und setStatemit dieser Variablen aufzurufen .

// same as 
this.state.a.push(6)
this.setState({})

Die meisten Leute machen das. Das ist so falsch . Dies bricht die Schönheit von React und macht Sie zu einem schlechten Programmierer.

Was ist der beste Weg, um mit Zuständen in React umzugehen? Lassen Sie mich erklären.

Wenn Sie "etwas" im vorhandenen Status ändern müssen, erhalten Sie zuerst eine Kopie dieses "etwas" aus dem aktuellen Status.

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// create a copy of this.state.a
// you can use ES6's destructuring or loadash's _.clone()
const currentStateCopy = [...this.state.a]

Durch Mutieren currentStateCopywird der ursprüngliche Zustand nicht mutiert. Führen Sie Operationen aus currentStateCopyund setzen Sie sie mit auf den neuen Status setState().

currentStateCopy.push(6)
this.state({
  a: currentStateCopy
})

Das ist wunderschön, oder?

Auf diese Weise werden alle Referenzen von this.state.aerst betroffen, wenn wir sie verwenden setState. Dies gibt Ihnen die Kontrolle über Ihren Code und hilft Ihnen dabei, elegante Tests zu schreiben und sich auf die Leistung des Codes in der Produktion zu verlassen.

Zur Beantwortung Ihrer Frage,

Warum kann ich den Status einer Komponente nicht direkt ändern?


Ja, das kannst du . Sie müssen sich jedoch den folgenden Konsequenzen stellen.

  1. Wenn Sie skalieren, schreiben Sie nicht verwaltbaren Code.
  2. Sie verlieren die Kontrolle stateüber alle Komponenten.
  3. Anstatt React zu verwenden, schreiben Sie benutzerdefinierte Codes über React.

Unveränderlichkeit ist nicht erforderlich, da JavaScript Single-Threaded ist. Aber es ist eine gute Übung, die Ihnen auf lange Sicht helfen wird.

PS. Ich habe ungefähr 10000 Zeilen veränderbaren React JS-Codes geschrieben. Wenn es jetzt kaputt geht, weiß ich nicht, wo ich suchen soll, da alle Werte irgendwo mutiert sind. Als ich das merkte, fing ich an, unveränderlichen Code zu schreiben. Vertrau mir! Das ist das Beste, was Sie mit einem Produkt oder einer App tun können.

Hoffe das hilft!

Pranesh Ravi
quelle
2
Nur zur Erinnerung: Die meisten grundlegenden Methoden zum Klonen in JS ( slice, ES6-Destrukturierung usw.) sind flach. Wenn Sie ein verschachteltes Array oder verschachtelte Objekte haben, müssen Sie sich andere Methoden zum tiefen Kopieren ansehen, z. B. JSON.parse(JSON.stringify(obj))(obwohl diese spezielle Methode nicht funktioniert, wenn Ihr Objekt Zirkelverweise enthält).
Galen Long
2
@Galen Long würde _.cloneDeepausreichen
Josh Broadhurst
2
@ JoshBroadhurst Toller Punkt, ja, das würde es. Und wenn jemand Bedenken hat, das gesamte Lodash-Paket für nur eine Funktion zu installieren, können Sie nur das cloneDeep-Modul mit installieren npm install lodash.clonedeepund mit verwenden let cloneDeep = require("lodash.clonedeep");.
Galen Long
5

die einfachste Antwort auf "

Warum kann ich den Status einer Komponente nicht direkt ändern:

dreht sich alles um die Aktualisierungsphase.

Wenn wir den Status einer Komponente aktualisieren, werden auch alle untergeordneten Komponenten gerendert. oder unser gesamter Komponentenbaum gerendert.

Aber wenn ich sage, dass unser gesamter Komponentenbaum gerendert wird, bedeutet das nicht, dass das gesamte DOM aktualisiert wird. Wenn eine Komponente gerendert wird, erhalten wir im Grunde ein Reaktionselement, das unseren virtuellen Dom aktualisiert.

React wird dann das virtuelle DOM betrachten, es hat auch eine Kopie des alten virtuellen DOM, deshalb sollten wir den Status nicht direkt aktualisieren , damit wir zwei verschiedene Objektreferenzen im Speicher haben können, wir haben das alte virtuelle DOM als sowie das neue virtuelle DOM.

Wenn Sie dann reagieren, wird herausgefunden, was geändert wurde, und basierend darauf wird das reale DOM entsprechend aktualisiert.

ich hoffe es hilft.

Fasil
quelle
1

Um zu vermeiden, dass Sie jedes Mal eine Kopie von erstellen this.state.element, können Sie ein Update mit $set or $pushoder vielen anderen von immutability-helper verwenden

z.B:

import update from 'immutability-helper';

const newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});
Alex
quelle
1

setState löst das Rendern der Komponenten aus. Wenn wir den Status immer wieder aktualisieren möchten, müssen wir setState festlegen, da dies sonst nicht ordnungsgemäß funktioniert.

Hashika Maduranga
quelle
-3

Mein derzeitiges Verständnis basiert auf dieser und dieser Antwort:

WENN Sie nicht verwenden shouldComponentUpdate oder andere Lifecycle - Methoden (wie componentWillReceiveProps, componentWillUpdateund componentDidUpdate) , wo Sie die alten und neuen Requisiten vergleichen / Zustand

DANN

Es ist in Ordnung zu mutieren stateund dann anzurufen setState(), sonst ist es nicht in Ordnung.

jhegedus
quelle
7
Beachten Sie, dass dies nur für die aktuelle und frühere Version von React gilt (derzeit in Version 15.3.2). Wer weiß, ob Ihr Code in einer zukünftigen Version beschädigt wird, da er auf Verhalten beruht, das als verboten dokumentiert ist.
TomW