Dieser Fehler ist ziemlich verwirrend:
https://twitter.com/kentcdodds/status/1102659818660102145
Ich denke, es passiert, weil fn
das von setInterval(fn, 0)
wurde, vor der von setRunning(false)
verursachten [running]
Effektbereinigung springt. Das Intervall wird also immer noch ausgelöst und ĂŒberschreibt setLapse(0)
, das wÀhrend des Ereignisses passiert ist, mit seinen setLapse(someValue)
.
Das erinnert mich an das in https://github.com/facebook/react/issues/14750#issuecomment -460409609 beschriebene Problem oder zumindest an einen Teil davon:
TatsĂ€chlich besteht dieses Problem sogar bei normalen React-TastenanschlĂ€gen (und anderen âdiskretenâ Ereignissen). Die Lösung dafĂŒr wĂ€re, passive Effekte zu spĂŒlen, bevor wir ein diskretes Ereignis erhalten.
Aber hier scheint dies nicht ausreichend zu sein, da der Effekt durch den Klick kippt, nicht vorher. Sollte also setState
innerhalb eines diskreten Events auch den passiven Effekt spĂŒlen? Scheint nicht. (Das wĂŒrde den Zweck, sie zu verzögern, zunichte machen.)
Dies funktioniert also wie geplant, und die Lösung ist nur useLayoutEffect
wenn das Timing wichtig ist? Oder die rAF-Lösung?
Das Problem ist, dass der Timer selbst kein diskretes Ereignis ist. Diskrete Ereignisse werden nur in Bezug auf andere diskrete Ereignisse garantiert. Ich glaube nicht, dass du das hier willst.
Das Stoppen des Timers ist ein asynchroner Vorgang, da alle gesetzten ZustĂ€nde asynchron sind. Es kann also noch etwas anderes reinkommen, bevor es gespĂŒlt wird.
Dies ist ein Fall, in dem useLayoutEffect das Problem nicht vollstĂ€ndig behebt. Im Concurrent-Modus sicher nicht, aber auch im Sync-Modus ist das lĂŒckenhaft. Wenn es sich nicht um einen Timer, sondern beispielsweise um ein Fokusereignis handelt, kann das innerhalb eines Stapels ausgelöst werden, bevor dieser geleert wird, was das gleiche Problem hĂ€tte.
setRunning(false); // I would like to add a stop of this timer to the queue to be performed later
setLapse(0); // I would like to add an operation to set lapse to zero later
// lots of random stuff that can happen before the batch flushes
// This might also queue an operation to set lapse to something else
// actual rendering
// If concurrent mode, lots of other random stuff that can happen while rendering
// This might also queue an operation to set lapse to something else
// Actually do all that work in order
Es geht darum, in welcher Reihenfolge Dinge in die Warteschlange aufgenommen werden.
Ein Ansatz besteht darin, den Reset erst dann auszulösen, wenn der Timer tatsĂ€chlich stoppt, dh im Effekt. Wenn Sie möchten, dass immer Null im selben Frame angezeigt wird, können Sie immer Null anzeigen, wenn die AusfĂŒhrung falsch ist.
Dieser ist jedoch wie fast immer besser als Reduzierer modelliert. Der Reduzierer kann das Verfallen von Aktualisierungen leicht ablehnen, wenn er denkt, dass er sich in einem gestoppten Zustand befindet, und auch die Logik ausfĂŒhren, um ihn tatsĂ€chlich zurĂŒckzusetzen.
Ich verstehe es jetzt. Vielen Dank.
https://mobile.twitter.com/dan_abramov/status/1104069236132057089
https://codesandbox.io/s/23m1659rlp
Hilfreichster Kommentar
Ein Ansatz besteht darin, den Reset erst dann auszulösen, wenn der Timer tatsĂ€chlich stoppt, dh im Effekt. Wenn Sie möchten, dass immer Null im selben Frame angezeigt wird, können Sie immer Null anzeigen, wenn die AusfĂŒhrung falsch ist.
Dieser ist jedoch wie fast immer besser als Reduzierer modelliert. Der Reduzierer kann das Verfallen von Aktualisierungen leicht ablehnen, wenn er denkt, dass er sich in einem gestoppten Zustand befindet, und auch die Logik ausfĂŒhren, um ihn tatsĂ€chlich zurĂŒckzusetzen.