Hält React die Reihenfolge für Statusaktualisierungen aufrecht?

139

Ich weiß, dass React Statusaktualisierungen zur Leistungsoptimierung asynchron und stapelweise durchführen kann. Daher können Sie niemals darauf vertrauen, dass der Status nach dem Aufruf aktualisiert wird setState. Aber vertrauen können Sie reagieren auf den Zustand in der gleichen Reihenfolge zu aktualisieren , wie setStateaufgerufen wird für

  1. die gleiche Komponente?
  2. verschiedene Komponenten?

Klicken Sie in den folgenden Beispielen auf die Schaltfläche:

1. Gibt es jemals die Möglichkeit, dass a falsch und b wahr ist für:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Gibt es jemals die Möglichkeit, dass a falsch und b wahr ist für:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Denken Sie daran, dass dies extreme Vereinfachungen meines Anwendungsfalls sind. Mir ist klar, dass ich dies anders machen kann, z. B. indem ich in Beispiel 1 beide Statusparameter gleichzeitig aktualisiere und die zweite Statusaktualisierung in einem Rückruf zur ersten Statusaktualisierung in Beispiel 2 durchführe. Dies ist jedoch nicht meine Frage. und ich bin nur daran interessiert, ob es eine genau definierte Art und Weise gibt, wie React diese Statusaktualisierungen durchführt, sonst nichts.

Jede durch Dokumentation gesicherte Antwort wird sehr geschätzt.

Darksmurf
quelle
3
Es scheint keine sinnlose Frage zu sein. Sie können diese Frage auch zu Github-Fragen der Reaktionsseite stellen. Dan Abramov ist dort normalerweise sehr hilfreich. Wenn ich so knifflige Fragen hatte, stellte ich sie und er antwortete. Schlecht ist, dass solche Probleme nicht wie in offiziellen Dokumenten öffentlich geteilt werden (so dass auch andere leicht darauf zugreifen können). Ich bin auch der Meinung, dass es in den offiziellen Dokumenten von React an einer umfassenden Berichterstattung über einige Themen wie das Thema Ihrer Frage usw.
mangelt
Nehmen wir zum Beispiel Folgendes : github.com/facebook/react/issues/11793 . Ich glaube, dass die in dieser Ausgabe diskutierten Dinge für viele Entwickler hilfreich sind, aber diese Dinge sind nicht in den offiziellen Dokumenten enthalten, da FB-Leute dies für fortgeschritten halten. Gleiches gilt möglicherweise für andere Dinge. Ich würde denken, dass ein offizieller Artikel mit dem Titel "Staatsmanagement in der Tiefe reagieren" oder "Fallstricke des Staatsmanagements", der alle Eckfälle des Staatsmanagements wie in Ihrer Frage untersucht, nicht schlecht wäre. Vielleicht können wir FB-Entwickler dazu drängen, die Dokumentation mit solchen
Dingen
Es gibt einen Link zu einem großartigen Artikel über Medium in meiner Frage. Es sollte 95% der staatlichen Anwendungsfälle abdecken. :)
Michal
2
@ Michael, aber dieser Artikel beantwortet diese Frage immer noch nicht IMHO
Giorgim

Antworten:

335

Ich arbeite an React.

TLDR:

Aber können Sie darauf vertrauen, dass React den Status in der Reihenfolge aktualisiert, in der setState aufgerufen wird?

  • die gleiche Komponente?

Ja.

  • verschiedene Komponenten?

Ja.

Die Reihenfolge der Updates wird immer eingehalten. Ob Sie einen Zwischenzustand "zwischen" ihnen sehen oder nicht, hängt davon ab, ob Sie sich in einem Stapel befinden oder nicht.

Derzeit (React 16 und früher) werden standardmäßig nur Aktualisierungen in React-Ereignishandlern gestapelt . Es gibt eine instabile API, um das Batching außerhalb von Ereignishandlern in seltenen Fällen zu erzwingen, wenn Sie es benötigen.

In zukünftigen Versionen (wahrscheinlich React 17 und höher) stapelt React standardmäßig alle Updates, sodass Sie nicht darüber nachdenken müssen. Wie immer werden wir diesbezügliche Änderungen im React-Blog und in den Versionshinweisen bekannt geben.


Der Schlüssel zum Verständnis ist, dass unabhängig davon, wie viele setState()Aufrufe in wie vielen Komponenten Sie in einem React-Ereignishandler ausführen , am Ende des Ereignisses nur ein einziges erneutes Rendern erzeugt wird . Dies ist entscheidend für eine gute Leistung in großen Anwendungen , denn wenn Childund Parentjeden Anruf , setState()wenn einem Klick Event - Handling, Sie wollen nicht wieder machen die Childzweimal.

In beiden Beispielen werden setState()Aufrufe in einem React-Ereignishandler ausgeführt. Daher werden sie am Ende des Ereignisses immer zusammengespült (und Sie sehen den Zwischenzustand nicht).

Die Aktualisierungen werden immer flach in der Reihenfolge zusammengeführt, in der sie auftreten . Wenn also das erste Update ist {a: 10}, ist das zweite {b: 20}und das dritte ist {a: 30}der gerenderte Zustand {a: 30, b: 20}. Das neuere Update auf denselben Statusschlüssel (z. B. wie ain meinem Beispiel) "gewinnt" immer.

Das this.stateObjekt wird aktualisiert, wenn die Benutzeroberfläche am Ende des Stapels erneut gerendert wird. Wenn Sie also den Status basierend auf einem vorherigen Status aktualisieren müssen (z. B. das Inkrementieren eines Zählers), sollten Sie die Funktionsversion verwenden setState(fn), die den vorherigen Status angibt, anstatt aus zu lesen this.state. Wenn Sie neugierig auf die Gründe dafür sind, habe ich dies in diesem Kommentar ausführlich erläutert .


In Ihrem Beispiel wird der "Zwischenstatus" nicht angezeigt, da wir uns in einem React-Ereignishandler befinden, in dem die Stapelverarbeitung aktiviert ist (weil React "weiß", wann wir dieses Ereignis beenden).

Sowohl in React 16 als auch in früheren Versionen gibt es jedoch außerhalb der React-Ereignishandler standardmäßig noch keine Stapelverarbeitung . Wenn wir in Ihrem Beispiel stattdessen einen AJAX-Antworthandler hätten handleClick, setState()würde jeder sofort verarbeitet, sobald dies geschieht. In diesem Fall ja, man würde einen Zwischenzustand sehen:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Wir wissen, dass es unpraktisch ist, dass das Verhalten unterschiedlich ist, je nachdem, ob Sie sich in einem Event-Handler befinden oder nicht . Dies wird sich in einer zukünftigen React-Version ändern, die standardmäßig alle Updates stapelt (und eine Opt-In-API bereitstellt, um Änderungen synchron zu löschen). Bis wir das Standardverhalten ändern (möglicherweise in React 17), gibt es eine API, mit der Sie das Batching erzwingen können :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Intern React-Ereignishandler werden alle eingeschlossen, unstable_batchedUpdatesweshalb sie standardmäßig gestapelt sind. Beachten Sie, dass das unstable_batchedUpdateszweimalige Umschließen eines Updates keine Auswirkungen hat. Die Aktualisierungen werden gelöscht, wenn wir den äußersten unstable_batchedUpdatesAnruf beenden .

Diese API ist "instabil" in dem Sinne, dass wir sie entfernen, wenn die Stapelverarbeitung bereits standardmäßig aktiviert ist. Wir werden es jedoch in einer Nebenversion nicht entfernen, sodass Sie sich bis React 17 sicher darauf verlassen können, wenn Sie in einigen Fällen das Stapeln außerhalb der React-Ereignishandler erzwingen müssen.


Zusammenfassend ist dies ein verwirrendes Thema, da standardmäßig nur Stapel in Ereignishandlern reagieren. Dies wird sich in zukünftigen Versionen ändern, und das Verhalten wird dann einfacher sein. Die Lösung besteht jedoch nicht darin, weniger zu stapeln , sondern standardmäßig mehr zu stapeln. Das werden wir tun.

Dan Abramov
quelle
1
Eine Möglichkeit, "immer die richtige Reihenfolge zu finden", besteht darin, ein temporäres Objekt zu erstellen, die verschiedenen Werte zuzuweisen (z. B. obj.a = true; obj.b = true) und am Ende einfach zu tun this.setState(obj). Dies ist sicher, unabhängig davon, ob Sie sich in einem Ereignishandler befinden oder nicht. Könnte ein guter Trick sein, wenn Sie häufig den Fehler machen, den Status mehrmals außerhalb von Ereignishandlern festzulegen.
Chris
Wir können uns also nicht darauf verlassen, dass die Stapelverarbeitung auf nur einen Ereignishandler beschränkt ist - wie Sie klargestellt haben, zumindest, weil dies nicht bald der Fall sein wird. Dann sollten wir setState mit Updater-Funktion verwenden, um Zugriff auf den neuesten Status zu erhalten, oder? Aber was ist, wenn ich einen state.filter verwenden muss, um eine XHR zum Lesen einiger Daten zu erstellen und diese dann in den Status zu versetzen? Sieht so aus, als müsste ich eine XHR mit verzögertem Rückruf (und damit einem Nebeneffekt) in einen Updater einfügen. Wird das dann als Best Practice angesehen?
Maksim Gumerov
1
Übrigens bedeutet das auch, dass wir überhaupt nicht aus diesem Zustand lesen sollten; Die einzig vernünftige Möglichkeit, einen state.X zu lesen, besteht darin, ihn in der Updater-Funktion anhand seines Arguments zu lesen. Und in diesen Zustand zu schreiben ist auch unsicher. Warum sollte man dann überhaupt Zugriff auf this.state gewähren? Diese sollten vielleicht eigenständige Fragen stellen, aber meistens versuche ich nur zu verstehen, ob ich die richtige Erklärung gefunden habe.
Maksim Gumerov
10
Diese Antwort sollte zur Dokumentation von reactjs.org hinzugefügt werden
Deen John
2
Könnten Sie bitte in diesem Beitrag klarstellen, ob "React Event Handler" componentDidUpdateund andere Lifecycle-Rückrufe ab React 16 enthält? Danke im Voraus!
Ivan
6

Dies ist eigentlich eine ziemlich interessante Frage, aber die Antwort sollte nicht zu kompliziert sein. Es gibt diesen großartigen Artikel auf Medium , der eine Antwort hat.

1) Wenn Sie dies tun

this.setState({ a: true });
this.setState({ b: true });

Ich denke nicht, dass es eine Situation geben awird, in der es aufgrund von Chargen sein wird trueund sein bwird .false

Allerdings , wenn bdavon abhängig ista ja dann könnte es eine Situation, wo Sie den erwarteten Zustand nicht erhalten würden.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Nachdem alle oben genannten Anrufe bearbeitet wurden this.state.value, ist 1, nicht 3, wie Sie es erwarten würden.

Dies wird im Artikel erwähnt: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Das wird uns geben this.state.value === 3

Michal
quelle
Was ist, wenn this.state.valuesowohl in Ereignishandlern (wo setStatewird gestapelt) als auch in AJAX-Rückrufen (wo setStatewird nicht gestapelt) aktualisiert wird. In Ereignishandlern würde ich das verwenden, updater functionum immer sicher zu sein, dass ich den Status unter Verwendung des aktuell aktualisierten Status aktualisiere, der von der Funktion bereitgestellt wird. Sollte ich setStateeine Updater-Funktion im Code des AJAX-Rückrufs verwenden, auch wenn ich weiß, dass dieser nicht gestapelt ist? Könnten Sie bitte die Verwendung setStateinnerhalb eines AJAX-Rückrufs mit oder ohne Verwendung einer Updater-Funktion klären? Danke dir!
Tonix
@Michal, hi Michal wollte nur eine Frage stellen, ist es wahr, dass wenn wir this.setState haben ({value: 0}); this.setState ({value: this.state.value + 1}); Der erste setState wird ignoriert und nur der zweite setState wird ausgeführt.
Dickens
@ Dickens Ich glaube, beide setStatewürden ausgeführt, aber der letzte würde gewinnen.
Michal
3

Mehrere Anrufe während desselben Zyklus können zusammen gestapelt werden. Wenn Sie beispielsweise versuchen, eine Artikelmenge mehr als einmal im selben Zyklus zu erhöhen, ergibt dies Folgendes:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html

Mosè Raguzzini
quelle
3

wie in doc

setState () stellt Änderungen am Komponentenstatus in die Warteschlange und teilt React mit, dass diese Komponente und ihre untergeordneten Komponenten mit dem aktualisierten Status neu gerendert werden müssen. Dies ist die primäre Methode, mit der Sie die Benutzeroberfläche als Reaktion auf Ereignishandler und Serverantworten aktualisieren.

Die Änderung wird wie in der Warteschlange ( FIFO : First In First Out) durchgeführt. Der erste Aufruf erfolgt zuerst

Ali
quelle
Hallo Ali, wollte nur eine Frage stellen, ist es wahr, wenn wir this.setState haben ({value: 0}); this.setState ({value: this.state.value + 1}); Der erste setState wird ignoriert und nur der zweite setState wird ausgeführt.
Dickens