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