React: RFKlarifikation: Warum ist `setState` asynchron?

Erstellt am 11. Nov. 2017  ·  31Kommentare  ·  Quelle: facebook/react

Ich habe eine ganze Weile versucht zu verstehen, warum setState asynchron ist. Da ich in der Vergangenheit keine Antwort darauf finden konnte, kam ich zu dem Schluss, dass es historische Gründe hatte und jetzt wahrscheinlich schwer zu ändern ist. @gaearon hat jedoch

Wie auch immer, hier sind die Gründe, die ich oft höre, aber ich denke, sie können nicht alles sein, da sie zu leicht zu bekämpfen sind

Async setState ist für asynchrones Rendering erforderlich

Viele denken zunächst, dass es an der Rendereffizienz liegt. Aber ich glaube nicht, dass dies der Grund für dieses Verhalten ist, denn das Synchronisieren von setState mit asynchronem Rendering klingt für mich trivial, etwa so:

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

Tatsächlich erlaubt beispielsweise mobx-react synchrone Zuweisungen zu Observablen und respektiert dennoch die asynchrone Natur des Renderings

Async setState wird benötigt, um zu wissen, welcher Zustand _gerendert_ war

Das andere Argument, das ich manchmal höre, ist, dass Sie über den Status, der _gerendert_ wurde, argumentieren möchten, nicht über den Status, der _angefragt_ wurde. Aber ich bin mir auch nicht sicher, ob dieses Prinzip viel Wert hat. Vom Konzept her fühlt es sich für mich seltsam an. Rendering ist ein Nebeneffekt, beim Zustand geht es um Fakten. Heute bin ich 32 Jahre alt und werde nächstes Jahr 33 Jahre alt, egal ob die besitzende Komponente dieses Jahr noch einmal rendert oder nicht :).

Um eine (wahrscheinlich nicht allzu gute) Parallele zu ziehen: Wenn Sie Ihre letzte Version eines selbstgeschriebenen Word-Dokuments nicht _lesen_ könnten, bis Sie es gedruckt haben, wäre das ziemlich umständlich. Ich glaube zum Beispiel, dass Game-Engines auch keine Rückmeldung darüber geben, welcher Stand des Spiels genau gerendert wurde und welche Frames ausgelassen wurden.

Eine interessante Beobachtung: In 2 Jahren mobx-react hat mir nie jemand die Frage gestellt: Woher weiß ich, dass meine Observablen gerendert werden? Diese Frage scheint nur sehr oft nicht relevant zu sein.

Ich bin auf einige Fälle gestoßen, in denen es relevant war zu wissen, welche Daten gerendert wurden. Der Fall, an den ich mich erinnere, war, dass ich die Pixelabmessungen einiger Daten für Layoutzwecke kennen musste. Aber das wurde mit didComponentUpdate elegant gelöst und war auch nicht wirklich darauf angewiesen, dass setState asynchron ist. Diese Fälle scheinen so selten zu sein, dass es kaum rechtfertigt, die API primär um sie herum zu entwerfen. Wenn es irgendwie geht , reicht es meiner Meinung nach


Ich habe keinen Zweifel, dass sich das React-Team der Verwirrung bewusst ist, die die asynchrone Natur von setState oft mit sich bringt, daher vermute ich, dass es einen weiteren sehr guten Grund für die aktuelle Semantik gibt. Erzähl mir mehr :)

Discussion

Hilfreichster Kommentar

Hier also ein paar Gedanken. Dies ist keineswegs eine vollständige Antwort, aber vielleicht ist dies immer noch hilfreicher, als nichts zu sagen.

Erstens denke ich, dass wir uns einig sind, dass es von Vorteil ist, den Abgleich zu verzögern setState() synchrones erneutes Rendern in vielen Fällen ineffizient wäre, und es ist besser, Aktualisierungen im Stapel zu erstellen, wenn wir wissen, dass wir wahrscheinlich mehrere erhalten werden.

Wenn wir uns beispielsweise in einem Browser-Handler click und sowohl Child als auch Parent setState aufrufen, möchten wir nicht erneut rendern die Child zweimal und ziehen es stattdessen vor, sie als schmutzig zu markieren und sie zusammen neu zu rendern, bevor das Browser-Ereignis beendet wird.

Sie fragen sich: Warum können wir nicht genau dasselbe tun (Batching), sondern setState Updates sofort in this.state schreiben, ohne auf das Ende der Abstimmung zu warten. Ich glaube nicht, dass es eine offensichtliche Antwort gibt (beide Lösungen haben Kompromisse), aber hier sind ein paar Gründe, die mir einfallen.

Gewährleistung der internen Konsistenz

Auch wenn state synchron aktualisiert wird, werden props nicht aktualisiert. (Sie können props erst kennen, wenn Sie die übergeordnete Komponente erneut rendern. Wenn Sie dies synchron tun, wird die Stapelverarbeitung beendet.)

Im Moment sind die von React bereitgestellten Objekte ( state , props , refs ) intern konsistent . Dies bedeutet, dass, wenn Sie nur diese Objekte verwenden, diese garantiert auf einen vollständig abgestimmten Baum verweisen (auch wenn es sich um eine ältere Version dieses Baums handelt). Warum ist das wichtig?

Wenn Sie nur den Zustand verwenden und dieser synchron geleert wird (wie Sie vorgeschlagen haben), würde dieses Muster funktionieren:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Angenommen, dieser Status muss aufgehoben werden, damit er von einigen Komponenten geteilt wird, damit Sie ihn zu einem übergeordneten Element verschieben:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Ich möchte hervorheben, dass dies in typischen React-Apps, die auf setState() basieren, die gängigste Art von React-spezifischem Refactoring ist, die Sie täglich durchführen würden .

Dies bricht jedoch unseren Code!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Dies liegt daran, dass in dem von Ihnen vorgeschlagenen Modell this.state sofort geleert würde, this.props jedoch nicht. Und wir können this.props nicht sofort leeren, ohne das übergeordnete Element erneut zu rendern, was bedeutet, dass wir auf die Stapelverarbeitung verzichten müssten (was je nach Fall die Leistung erheblich beeinträchtigen kann).

Es gibt auch subtilere Fälle, wie dies kaputt gehen kann, z. B. wenn Sie Daten aus props (noch nicht geleert) und state (sofort geleert) mischen, um einen neuen Zustand zu erstellen : https://github.com/facebook/react/issues/122#issuecomment -81856416. Refs weisen das gleiche Problem auf: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Diese Beispiele sind keineswegs theoretisch. Tatsächlich hatten React Redux-Bindungen genau diese Art von Problem, weil sie React-Requisiten mit Nicht-React-Status mischen: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ Reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Ich weiß nicht, warum MobX-Benutzer nicht darauf gestoßen sind, aber meine Intuition ist, dass sie möglicherweise auf solche Szenarien stoßen, sie aber als ihre eigene Schuld betrachten. Oder vielleicht lesen sie nicht so viel von props und stattdessen direkt von veränderlichen MobX-Objekten.

Wie löst React das heute? In React werden sowohl this.state als auch this.props erst nach der Abstimmung und dem Flush aktualisiert, sodass Sie sehen würden, dass 0 sowohl vor als auch nach dem Refactoring gedruckt wird. Dies macht den Hebezustand sicher.

Ja, dies kann in manchen Fällen unbequem sein. Vor allem für Leute mit mehr OO-Hintergrund, die nur den Zustand mehrmals mutieren möchten, anstatt darüber nachzudenken, wie man eine vollständige Zustandsaktualisierung an einem einzigen Ort darstellt. Ich kann das nachempfinden, obwohl ich denke, dass es aus der Debugging-Perspektive klarer ist, die Statusaktualisierungen konzentriert zu halten: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Sie haben jedoch die Möglichkeit, den Status, den Sie sofort einlesen möchten , in ein seitwärts veränderbares Objekt zu verschieben, insbesondere wenn Sie ihn nicht als Quelle der Wahrheit für das Rendern verwenden. Das ist so ziemlich das, was Sie mit MobX tun können 🙂.

Sie haben auch die Möglichkeit, den gesamten Baum zu ReactDOM.flushSync(fn) . Ich glaube, wir haben es noch nicht dokumentiert, aber wir werden dies definitiv irgendwann während des 16.x-Release-Zyklus tun. Beachten Sie, dass es tatsächlich ein vollständiges Neu-Rendering für Aktualisierungen erzwingt, die innerhalb des Aufrufs erfolgen, daher sollten Sie es sehr sparsam verwenden. Auf diese Weise wird die Garantie der internen Konsistenz zwischen props , state und refs .

Zusammenfassend lässt sich sagen , dass

Gleichzeitige Updates aktivieren

Vom Konzept her verhält sich React so, als hätte es eine einzelne Update-Warteschlange pro Komponente. Aus diesem Grund ist die Diskussion überhaupt sinnvoll: Wir diskutieren, ob Updates auf this.state sofort angewendet werden sollen oder nicht, da wir keine Zweifel daran haben, dass die Updates in genau dieser Reihenfolge angewendet werden. Das muss aber nicht sein ( haha ).

In letzter Zeit haben wir viel über „async Rendering“ gesprochen. Ich gebe zu, wir haben nicht sehr gut kommuniziert, was das bedeutet, aber das liegt in der Natur von F&E: Man verfolgt eine Idee, die konzeptionell vielversprechend erscheint, aber man versteht ihre Implikationen erst, wenn man genügend Zeit damit verbracht hat.

Wir haben das „asynchrone Rendering“ unter anderem dadurch erklärt, dass React setState() Aufrufen unterschiedliche Prioritäten zuweisen kann, je nachdem, woher sie kommen: ein Ereignishandler, eine Netzwerkantwort, eine Animation usw .

Wenn Sie beispielsweise eine Nachricht eingeben, müssen setState() Aufrufe in der TextBox Komponente sofort geleert werden. Wenn Sie jedoch während des Tippens eine neue Nachricht erhalten, ist es wahrscheinlich besser, das Rendern des neuen MessageBubble bis zu einem bestimmten Schwellenwert (z Faden.

Wenn wir bestimmten Updates eine „niedrigere Priorität“ zuweisen, könnten wir ihr Rendering in kleine Abschnitte von wenigen Millisekunden aufteilen, damit sie für den Benutzer nicht wahrnehmbar sind.

Ich weiß, dass Leistungsoptimierungen wie diese nicht sehr aufregend oder überzeugend klingen. Man könnte sagen: „Das brauchen wir bei MobX nicht, unser Update-Tracking ist schnell genug, um Re-Renderings zu vermeiden“. Ich glaube nicht, dass dies in allen Fällen stimmt (zB egal wie schnell MobX ist, Sie müssen immer noch DOM-Knoten erstellen und das Rendering für neu gemountete Ansichten durchführen). Wenn es jedoch wahr wäre und Sie sich bewusst dafür entschieden haben, dass Sie Objekte immer in eine bestimmte JavaScript-Bibliothek einschließen, die Lese- und Schreibvorgänge verfolgt, profitieren Sie möglicherweise nicht so sehr von diesen Optimierungen.

Beim asynchronen Rendering geht es jedoch nicht nur um Leistungsoptimierungen.

Betrachten Sie beispielsweise den Fall, dass Sie von einem Bildschirm zum anderen navigieren. Normalerweise würden Sie ein Spinner anzeigen, während der neue Bildschirm gerendert wird.

Wenn die Navigation jedoch schnell genug ist (innerhalb einer Sekunde oder so), führt das Blinken und sofortige Ausblenden eines Spinners zu einer verschlechterten Benutzererfahrung. Schlimmer noch, wenn Sie mehrere Ebenen von Komponenten mit unterschiedlichen asynchronen Abhängigkeiten (Daten, Code, Bilder) haben, erhalten Sie am Ende eine Kaskade von Spinnern, die kurz nacheinander blinken. Das ist optisch unangenehm und macht Ihre App in der Praxis durch all die DOM-Reflows langsamer. Es ist auch die Quelle vieler Boilerplate-Codes.

Wäre es nicht schön, wenn wir bei einem einfachen setState() , das eine andere Ansicht rendert, mit dem Rendern der aktualisierten Ansicht „im Hintergrund“ „starten“ könnten? Stellen Sie sich vor, Sie könnten ,

Es stellt sich heraus, dass wir dies mit dem aktuellen React-Modell und einigen Anpassungen an den Lebenszyklen tatsächlich umsetzen können! @acdlite hat in den letzten Wochen an dieser Funktion gearbeitet und wird in Kürze einen RFC dafür veröffentlichen.

Beachten Sie, dass dies nur möglich ist, weil this.state nicht sofort geleert wird. Wenn es sofort geleert würde, hätten wir keine Möglichkeit, eine „neue Version“ der Ansicht im Hintergrund zu rendern, während die „alte Version“ noch sichtbar und interaktiv ist. Ihre unabhängigen Zustandsaktualisierungen würden kollidieren.

Ich möchte @acdlite nicht den Donner stehlen, um nicht sofort Statusaktualisierungen Spülung.

Alle 31 Kommentare

Wir warten alle @gaearon .

@Kaybarax Hey, es ist Wochenende ;-)

@mweststrate Oh! mein Fehler. Cool.

Ich werde hier auf die Nerven gehen und sagen, dass es daran liegt, dass mehrere setState s in einem Tick zusammengefasst werden.

Ich fahre nächste Woche in den Urlaub, aber wahrscheinlich am Dienstag, also werde ich versuchen, am Montag zu antworten.

Funktion enqueueUpdate(Komponente) {
sicherstellenInjected();

// Verschiedene Teile unseres Codes (wie der von ReactCompositeComponent
// _renderValidatedComponent) nehmen an, dass Aufrufe zum Rendern nicht verschachtelt sind;
// Überprüfen Sie, ob dies der Fall ist. (Dies wird von jedem Top-Level-Update aufgerufen
// Funktion, wie setState, forceUpdate usw.; Schöpfung und
// Zerstörung von Top-Level-Komponenten wird in ReactMount bewacht.)

if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, Komponente);
Rückkehr;
}

dirtyComponents.push(Komponente);
if (Komponente._updateBatchNumber == null) {
Komponente._updateBatchNumber = updateBatchNumber + 1;
}
}

@mweststrate nur 2 Cent: das ist eine sehr
Ich bin sicher, wir sind uns alle einig, dass es viel einfacher wäre, über den Zustand nachzudenken, wenn setState synchron wäre.
Was auch immer die Gründe waren, setState asynchron zu machen, ich bin mir nicht sicher, ob das Team gut reagiert, verglichen mit den Nachteilen, die sich daraus ergeben würden, z.

@mweststrate interessanterweise habe ich die gleiche Frage hier gestellt: https://discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487

Ich persönlich hatte und habe bei anderen Entwicklern Verwirrung zu diesem Thema gesehen. @gaearon , wenn du etwas Zeit

Entschuldigung, es ist das Ende des Jahres und wir waren bei GitHub usw. etwas im Rückstand, als wir versuchten, alles, woran wir vor den Feiertagen gearbeitet haben, abzuschließen.

Ich beabsichtige, auf diesen Thread zurückzukommen und darüber zu diskutieren. Aber es ist auch ein bewegliches Ziel, da wir derzeit an asynchronen React-Funktionen arbeiten, die sich direkt darauf beziehen, wie und wann this.state aktualisiert wird. Ich möchte nicht viel Zeit damit verbringen, etwas zu schreiben und es dann neu schreiben zu müssen, weil sich die zugrunde liegenden Annahmen geändert haben. Ich würde das gerne offen halten, weiß aber noch nicht, wann ich eine definitive Antwort geben kann.

Hier also ein paar Gedanken. Dies ist keineswegs eine vollständige Antwort, aber vielleicht ist dies immer noch hilfreicher, als nichts zu sagen.

Erstens denke ich, dass wir uns einig sind, dass es von Vorteil ist, den Abgleich zu verzögern setState() synchrones erneutes Rendern in vielen Fällen ineffizient wäre, und es ist besser, Aktualisierungen im Stapel zu erstellen, wenn wir wissen, dass wir wahrscheinlich mehrere erhalten werden.

Wenn wir uns beispielsweise in einem Browser-Handler click und sowohl Child als auch Parent setState aufrufen, möchten wir nicht erneut rendern die Child zweimal und ziehen es stattdessen vor, sie als schmutzig zu markieren und sie zusammen neu zu rendern, bevor das Browser-Ereignis beendet wird.

Sie fragen sich: Warum können wir nicht genau dasselbe tun (Batching), sondern setState Updates sofort in this.state schreiben, ohne auf das Ende der Abstimmung zu warten. Ich glaube nicht, dass es eine offensichtliche Antwort gibt (beide Lösungen haben Kompromisse), aber hier sind ein paar Gründe, die mir einfallen.

Gewährleistung der internen Konsistenz

Auch wenn state synchron aktualisiert wird, werden props nicht aktualisiert. (Sie können props erst kennen, wenn Sie die übergeordnete Komponente erneut rendern. Wenn Sie dies synchron tun, wird die Stapelverarbeitung beendet.)

Im Moment sind die von React bereitgestellten Objekte ( state , props , refs ) intern konsistent . Dies bedeutet, dass, wenn Sie nur diese Objekte verwenden, diese garantiert auf einen vollständig abgestimmten Baum verweisen (auch wenn es sich um eine ältere Version dieses Baums handelt). Warum ist das wichtig?

Wenn Sie nur den Zustand verwenden und dieser synchron geleert wird (wie Sie vorgeschlagen haben), würde dieses Muster funktionieren:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Angenommen, dieser Status muss aufgehoben werden, damit er von einigen Komponenten geteilt wird, damit Sie ihn zu einem übergeordneten Element verschieben:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Ich möchte hervorheben, dass dies in typischen React-Apps, die auf setState() basieren, die gängigste Art von React-spezifischem Refactoring ist, die Sie täglich durchführen würden .

Dies bricht jedoch unseren Code!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Dies liegt daran, dass in dem von Ihnen vorgeschlagenen Modell this.state sofort geleert würde, this.props jedoch nicht. Und wir können this.props nicht sofort leeren, ohne das übergeordnete Element erneut zu rendern, was bedeutet, dass wir auf die Stapelverarbeitung verzichten müssten (was je nach Fall die Leistung erheblich beeinträchtigen kann).

Es gibt auch subtilere Fälle, wie dies kaputt gehen kann, z. B. wenn Sie Daten aus props (noch nicht geleert) und state (sofort geleert) mischen, um einen neuen Zustand zu erstellen : https://github.com/facebook/react/issues/122#issuecomment -81856416. Refs weisen das gleiche Problem auf: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Diese Beispiele sind keineswegs theoretisch. Tatsächlich hatten React Redux-Bindungen genau diese Art von Problem, weil sie React-Requisiten mit Nicht-React-Status mischen: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ Reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Ich weiß nicht, warum MobX-Benutzer nicht darauf gestoßen sind, aber meine Intuition ist, dass sie möglicherweise auf solche Szenarien stoßen, sie aber als ihre eigene Schuld betrachten. Oder vielleicht lesen sie nicht so viel von props und stattdessen direkt von veränderlichen MobX-Objekten.

Wie löst React das heute? In React werden sowohl this.state als auch this.props erst nach der Abstimmung und dem Flush aktualisiert, sodass Sie sehen würden, dass 0 sowohl vor als auch nach dem Refactoring gedruckt wird. Dies macht den Hebezustand sicher.

Ja, dies kann in manchen Fällen unbequem sein. Vor allem für Leute mit mehr OO-Hintergrund, die nur den Zustand mehrmals mutieren möchten, anstatt darüber nachzudenken, wie man eine vollständige Zustandsaktualisierung an einem einzigen Ort darstellt. Ich kann das nachempfinden, obwohl ich denke, dass es aus der Debugging-Perspektive klarer ist, die Statusaktualisierungen konzentriert zu halten: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Sie haben jedoch die Möglichkeit, den Status, den Sie sofort einlesen möchten , in ein seitwärts veränderbares Objekt zu verschieben, insbesondere wenn Sie ihn nicht als Quelle der Wahrheit für das Rendern verwenden. Das ist so ziemlich das, was Sie mit MobX tun können 🙂.

Sie haben auch die Möglichkeit, den gesamten Baum zu ReactDOM.flushSync(fn) . Ich glaube, wir haben es noch nicht dokumentiert, aber wir werden dies definitiv irgendwann während des 16.x-Release-Zyklus tun. Beachten Sie, dass es tatsächlich ein vollständiges Neu-Rendering für Aktualisierungen erzwingt, die innerhalb des Aufrufs erfolgen, daher sollten Sie es sehr sparsam verwenden. Auf diese Weise wird die Garantie der internen Konsistenz zwischen props , state und refs .

Zusammenfassend lässt sich sagen , dass

Gleichzeitige Updates aktivieren

Vom Konzept her verhält sich React so, als hätte es eine einzelne Update-Warteschlange pro Komponente. Aus diesem Grund ist die Diskussion überhaupt sinnvoll: Wir diskutieren, ob Updates auf this.state sofort angewendet werden sollen oder nicht, da wir keine Zweifel daran haben, dass die Updates in genau dieser Reihenfolge angewendet werden. Das muss aber nicht sein ( haha ).

In letzter Zeit haben wir viel über „async Rendering“ gesprochen. Ich gebe zu, wir haben nicht sehr gut kommuniziert, was das bedeutet, aber das liegt in der Natur von F&E: Man verfolgt eine Idee, die konzeptionell vielversprechend erscheint, aber man versteht ihre Implikationen erst, wenn man genügend Zeit damit verbracht hat.

Wir haben das „asynchrone Rendering“ unter anderem dadurch erklärt, dass React setState() Aufrufen unterschiedliche Prioritäten zuweisen kann, je nachdem, woher sie kommen: ein Ereignishandler, eine Netzwerkantwort, eine Animation usw .

Wenn Sie beispielsweise eine Nachricht eingeben, müssen setState() Aufrufe in der TextBox Komponente sofort geleert werden. Wenn Sie jedoch während des Tippens eine neue Nachricht erhalten, ist es wahrscheinlich besser, das Rendern des neuen MessageBubble bis zu einem bestimmten Schwellenwert (z Faden.

Wenn wir bestimmten Updates eine „niedrigere Priorität“ zuweisen, könnten wir ihr Rendering in kleine Abschnitte von wenigen Millisekunden aufteilen, damit sie für den Benutzer nicht wahrnehmbar sind.

Ich weiß, dass Leistungsoptimierungen wie diese nicht sehr aufregend oder überzeugend klingen. Man könnte sagen: „Das brauchen wir bei MobX nicht, unser Update-Tracking ist schnell genug, um Re-Renderings zu vermeiden“. Ich glaube nicht, dass dies in allen Fällen stimmt (zB egal wie schnell MobX ist, Sie müssen immer noch DOM-Knoten erstellen und das Rendering für neu gemountete Ansichten durchführen). Wenn es jedoch wahr wäre und Sie sich bewusst dafür entschieden haben, dass Sie Objekte immer in eine bestimmte JavaScript-Bibliothek einschließen, die Lese- und Schreibvorgänge verfolgt, profitieren Sie möglicherweise nicht so sehr von diesen Optimierungen.

Beim asynchronen Rendering geht es jedoch nicht nur um Leistungsoptimierungen.

Betrachten Sie beispielsweise den Fall, dass Sie von einem Bildschirm zum anderen navigieren. Normalerweise würden Sie ein Spinner anzeigen, während der neue Bildschirm gerendert wird.

Wenn die Navigation jedoch schnell genug ist (innerhalb einer Sekunde oder so), führt das Blinken und sofortige Ausblenden eines Spinners zu einer verschlechterten Benutzererfahrung. Schlimmer noch, wenn Sie mehrere Ebenen von Komponenten mit unterschiedlichen asynchronen Abhängigkeiten (Daten, Code, Bilder) haben, erhalten Sie am Ende eine Kaskade von Spinnern, die kurz nacheinander blinken. Das ist optisch unangenehm und macht Ihre App in der Praxis durch all die DOM-Reflows langsamer. Es ist auch die Quelle vieler Boilerplate-Codes.

Wäre es nicht schön, wenn wir bei einem einfachen setState() , das eine andere Ansicht rendert, mit dem Rendern der aktualisierten Ansicht „im Hintergrund“ „starten“ könnten? Stellen Sie sich vor, Sie könnten ,

Es stellt sich heraus, dass wir dies mit dem aktuellen React-Modell und einigen Anpassungen an den Lebenszyklen tatsächlich umsetzen können! @acdlite hat in den letzten Wochen an dieser Funktion gearbeitet und wird in Kürze einen RFC dafür veröffentlichen.

Beachten Sie, dass dies nur möglich ist, weil this.state nicht sofort geleert wird. Wenn es sofort geleert würde, hätten wir keine Möglichkeit, eine „neue Version“ der Ansicht im Hintergrund zu rendern, während die „alte Version“ noch sichtbar und interaktiv ist. Ihre unabhängigen Zustandsaktualisierungen würden kollidieren.

Ich möchte @acdlite nicht den Donner stehlen, um nicht sofort Statusaktualisierungen Spülung.

Wunderbare ausführliche Erklärung der Entscheidungen hinter der Architektur von React. Danke.

markieren

Danke, Dan.

Ich ❤️ dieses Problem. Tolle Frage und tolle Antwort. Ich dachte immer, das sei eine schlechte Designentscheidung, jetzt muss ich umdenken 😄

Danke, Dan.

Ich nenne es asyncAwesome setState :smile:

Ich neige dazu, zu denken, dass zuerst alles asynchron implementiert werden sollte, und wenn Sie eine Notwendigkeit für einen Synchronisierungsvorgang feststellen, schließen Sie den asynchronen Vorgang mit einem Warten auf den Abschluss ein. Es ist viel einfacher, aus asynchronem Code Sync-Code zu machen (alles, was Sie brauchen, ist ein Wrapper) als umgekehrt (was im Grunde ein komplettes Neuschreiben erfordert, es sei denn, Sie greifen nach Threading, was überhaupt nicht leicht ist).

@gaearon danke für die ausführliche Erklärung! Es nervt mich schon lange ("es muss einen guten Grund geben, aber keiner kann sagen welchen"). Aber jetzt macht es total Sinn und ich sehe, dass dies eine wirklich bewusste Entscheidung ist :). Vielen Dank für die ausführliche Antwort, freut mich sehr!

Oder vielleicht lesen sie nicht so viel von Requisiten und stattdessen direkt von veränderlichen MobX-Objekten.

Ich denke, das stimmt in der Tat, in MobX werden Requisiten normalerweise nur als Komponentenkonfiguration verwendet, und die Domänendaten werden normalerweise nicht in Requisiten erfasst, sondern in Domänenentitäten, die zwischen Komponenten weitergegeben werden.

Nochmals vielen Dank!

@gaearon Danke für die ausführliche und tolle Erklärung.
Obwohl hier noch etwas fehlt, was ich glaube, verstanden zu haben, aber sicher sein möchte.

Wenn das Ereignis als "Outside React" registriert ist, bedeutet dies beispielsweise durch addEventListener auf einem Ref. Dann findet kein Batching statt.
Betrachten Sie diesen Code:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Wenn wir auf die Schaltfläche "Ereignis reagieren" klicken, sehen wir in der Konsole:
"before setState" 0
"after setState" 0
Wenn die andere Schaltfläche "Direct DOM event" angeklickt wird, sehen wir in der Konsole:
"before setState" 0
"after setState" 1

Nach einigen kleinen Recherchen und dem Durchsuchen des Quellcodes denke ich, dass ich weiß, warum dies passiert.
React kann den Ablauf des Ereignisses nicht vollständig kontrollieren und kann nicht sicher sein, wann und wie das nächste Ereignis ausgelöst wird, so dass es als "Panik-Modus" den Zustandswechsel sofort auslöst.

Was ist Ihre Meinung dazu? :Denken:

@sag1v Obwohl ein bisschen verwandt, ist es wahrscheinlich klarer, eine neue Ausgabe für neue Fragen zu eröffnen. Verwenden Sie einfach #11527 irgendwo in der Beschreibung, um es mit dieser zu verknüpfen.

@sag1v @gaearon hatte mir hier eine sehr knappe Antwort gegeben https://twitter.com/dan_abramov/status/949992957180104704 . Ich denke, seine Einstellung dazu würde mir auch konkretere Antworten geben.

@mweststrate Ich dachte daran, ein neues Problem zu eröffnen, aber dann wurde mir klar, dass dies direkt mit Ihrer Frage zusammenhängt "Warum ist setState asynchron?".
Da es in dieser Diskussion um die Entscheidungen geht, die getroffen werden, um setState "async" zu machen, dachte ich, wann und warum es "sync" ist.
Es macht mir nichts aus, ein neues Thema zu eröffnen, wenn ich Sie nicht davon überzeugt habe, dass mein Beitrag mit diesem Thema zusammenhängt :wink:

@Kaybarax Das liegt daran, dass Ihre Frage "_When is it sync_" und nicht "_ Why is it sync_" war.
Wie ich in meinem Beitrag erwähnt habe, glaube ich,

Reagieren kann den Ablauf des Ereignisses nicht vollständig kontrollieren und kann nicht sicher sein, wann und wie das nächste Ereignis ausgelöst wird, also löst es als "Panik-Modus" nur den Zustandswechsel sofort aus

Irgendwie. Obwohl dies nicht genau mit der Frage zum Aktualisieren von this.state .

Sie fragen, in welchen Fällen React Batching aktiviert. React führt derzeit Batch-Updates in von React verwalteten Event-Handlern durch, da React im obersten Aufruf-Stack-Frame „sitzt“ und weiß, wann alle React-Event-Handler ausgeführt wurden. An diesem Punkt wird das Update geleert.

Wenn der Event-Handler nicht von React eingerichtet wurde, macht er das Update derzeit synchron. Weil es nicht weiß, ob es sicher ist, zu warten oder nicht, und ob bald andere Updates kommen.

In zukünftigen Versionen von React wird sich dieses Verhalten ändern. Der Plan sieht vor, Updates standardmäßig mit niedriger Priorität zu behandeln, sodass sie am Ende zusammengeführt und zusammengeführt werden (zB innerhalb einer Sekunde), mit der Möglichkeit, sie sofort zu leeren. Hier können Sie mehr lesen: https://github.com/facebook/react/issues/11171#issuecomment -357945371.

Genial!

Diese Frage und Antwort sollte an einem besser erreichbaren Ort dokumentiert werden. Danke Jungs, die uns aufklären.

Viel gelernt . Danke

Ich versuche, meinen Standpunkt in den Thread einzubringen. Ich arbeite seit einigen Monaten an einer App, die auf MobX basiert, erforsche ClojureScript seit Jahren und habe meine eigene React-Alternative (genannt Respo) entwickelt, ich habe Redux in der Anfangszeit, wenn auch in sehr kurzer Zeit, ausprobiert und setze auf ReasonML.

Die Kernidee bei der Kombination von React und Functional Programming (FP) besteht darin, dass Sie ein Datenelement erhalten, das Sie mit Ihren Fähigkeiten in eine Ansicht rendern können, die den Gesetzen in FP gehorcht. Sie haben keine Nebenwirkungen, wenn Sie nur reine Funktionen verwenden.

React ist nicht rein funktional. Durch die Einbeziehung lokaler Zustände in Komponenten hat React die Möglichkeit, mit verschiedenen Bibliotheken im Zusammenhang mit DOM und anderen Browser-APIs (auch MobX-freundlich) zu interagieren, was React inzwischen unrein macht. Ich habe es jedoch in ClojureScript versucht, wenn React rein ist, könnte es eine Katastrophe sein, da es wirklich schwierig ist, mit so vielen vorhandenen Bibliotheken zu interagieren, die Nebenwirkungen haben.

In Respo (meiner eigenen Lösung) hatte ich also zwei Ziele, die widersprüchlich erscheinen: 1) view = f(store) also wird kein lokaler Staat erwartet; 2) Ich mag es nicht, alle Komponenten-UI-Zustände in globalen Reduzierern zu programmieren, da dies schwer zu warten sein könnte. Am Ende habe ich herausgefunden, dass ich einen Syntax-Zucker brauche, der mir hilft, Komponentenzustände in einem globalen Speicher mit paths , während ich Zustandsaktualisierungen innerhalb der Komponente mit Clojure-Makros schreibe.

Was ich also gelernt habe: Local States ist ein Feature für die Entwicklererfahrung, darunter wollen wir globale Zustände, die es unseren Engines ermöglichen, Optimierungen in tiefen Ebenen durchzuführen. Also bevorzugen MobX-Leute OOP, ist das für Entwicklererfahrung oder für Engines?

Übrigens habe ich einen Vortrag über die Zukunft von React und seinen asynchronen Funktionen gehalten, falls Sie es verpasst haben:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Dan, du bist mein Idol.....vielen Dank.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen