-
Notifications
You must be signed in to change notification settings - Fork 46k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Animation priority #9968
Conversation
d0d37df
to
ba3e089
Compare
The first commit isn't super-related. It adds a new ReactNoop API for testing, which I used to test this bug fix. The commit message has details. |
Breaks the build for
Maybe need to add it here. |
ba3e089
to
98bb9ba
Compare
Allows us to make assertions on the values that are yielded when performing work. In our existing tests, we do this manually by pushing into an array. ReactNoop.flushThrough extends this concept. It accepts an array of expected values and flushes until those values are yielded.
98bb9ba
to
0375526
Compare
0375526
to
27aefc5
Compare
yarn.lock
Outdated
dependencies: | ||
chalk "^1.1.3" | ||
pretty-format "^20.0.3" | ||
|
||
jest-matchers@^19.0.0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to download two of these questionable deps? Great.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can change the version constraint ¯\_(ツ)_/¯ It's just the noop renderer
scheduleAnimationCallback: ReactDOMFrameScheduling.rAF, | ||
|
||
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, | ||
scheduleCallback: ReactDOMFrameScheduling.rIC, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is too vague. scheduleDeferredCallback
?
We could call it requestIdleCallback
but I suspect we don't actually want to be forced to pass the concept of a deadline to this but rather ask it "shouldIContinue()`? To allow for environments that are not timer based but input based.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scheduleAsyncCallback
? Seems redundant, but so does "deferred" :D Will we ever schedule more than one type of callback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember that this is a public API for renderers to use. It should first and foremost describe what kind of function they're supposed to provide here.
Async isn't quite enough since that could be a micro-task tick (like the Promise queue).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just call it scheduleDeferredCallback
for now but idk how descriptive that actually is.
switch (nextPriorityLevel) { | ||
case SynchronousPriority: | ||
case TaskPriority: | ||
// We have remaining syncrhonous or task work. Keep performing it, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
|
||
let keepPerformingWork = true; | ||
let hasRemainingAsyncWork = false; | ||
while (keepPerformingWork && fatalError === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the intention here with all the possible states (9) these can be in? I have trouble following the extra flags. The flags are indicative of a code smell. Can you refactor this in terms of control flow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically this is a result of me trying to both 1) avoid while (true)
, and 2) not repeat the same checks that are done in workLoop
. Should I just use while (true)
and break
/ continue
?
I could get rid of hasRemainingAsyncWork
by scheduling the callback inside this loop instead of once we exit it. That's how I did it originally but I found this easier to follow. I'll change it back.
case TaskPriority: | ||
// TODO: If we're not already performing work, schedule a | ||
// deferred callback. | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did this return get lost? The TODO above seems wrong to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the topic I brought up at lunch. I believe you're the one who suggested this to me, and why I added this comment :)
Seems correct to me that if we setState
at Task priority, but we're not already performing work, we should schedule a callback and perform it later. The other option is to treat it as synchronous work, I suppose. We need to decide whether "task" priority should be treated as semantically different from sync priority for non-nested updates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would schedule an unnecessary deferred callback if you call setState in componentDidMount inside a sync render.
Would be nice to have a test for that like Dan's ReactIncrementalPerf-test.js.snap that spawned these other changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a snapshot test
@@ -309,37 +267,6 @@ describe('ReactIncrementalScheduling', () => { | |||
expect(ReactNoop.getChildren()).toEqual([span(5)]); | |||
}); | |||
|
|||
it('does not perform animation work after time runs out', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this still a valuable test but for HighPriority
instead of AnimationPriority
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe? Why didn't we already have a HighPriority
test then? My impression was that this test existed because of the different semantics for Animation priority versus async/deferred priorities.
class Foo extends React.Component { | ||
state = {}; | ||
componentDidUpdate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why delete this from the assertion? It has nothing to do with the ReactNoop.yield
stuff so you've lost a part of the test, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asserted on the children instead.
expect(ReactNoop.getChildren()).toEqual([span('')]);
There's no advantage to scheduling updates with animation priority versus scheduling sync work inside requestAnimationCallback. So we can remove all the animation-specific code. Now there's only one type of callback.
27aefc5
to
acaf7b2
Compare
@sebmarkbage Updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add unit tests for the things we found might break. :)
} | ||
} | ||
|
||
function performWork( | ||
priorityLevel: PriorityLevel, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We realized that this is necessary but missing a unit test.
case TaskPriority: | ||
// TODO: If we're not already performing work, schedule a | ||
// deferred callback. | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would schedule an unnecessary deferred callback if you call setState in componentDidMount inside a sync render.
Would be nice to have a test for that like Dan's ReactIncrementalPerf-test.js.snap that spawned these other changes.
@sebmarkbage I think I addressed everything |
d3b1b85
to
dabfaf5
Compare
const previousIsBatchingUpdates = isBatchingUpdates; | ||
// This is only true if we're nested inside either batchedUpdates or a | ||
// lifecycle method. | ||
isUnbatchingUpdates = isBatchingUpdates || isPerformingWork; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this set if isPerformingWork
and then the consequence is ignored if isPerformingWork
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this check is redundant. I added this check first, then realized it made more sense to check isPerformingWork
in scheduleUpdate
, but forgot to remove the check here. I'll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Behavior now matches Stack. It's unfortunate that this prevents us from unifying SynchronousPriority and TaskPriority.
dabfaf5
to
045a9cb
Compare
// batchedUpdates or a lifecycle. We should only flush | ||
// synchronous work, not task work. | ||
performWork(SynchronousPriority, null); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this branch needed? Why aren't we just scheduling with TaskPriority if it is supposed to be Task priority?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline, but documenting here for anyone watching.
Both branches are performing synchronous work. What's different is the first parameter to performWork
, which specifies the minimum level of work that should be performed. In the normal case, we should perform both task and synchronous work. In the isUnbatchingUpdates
case, we should only perform synchronous work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I hope this is correct. Let's keep iterating.
Follow up to this comment: #9952 (comment)
If we're already performing work, we shouldn't schedule an additional callback, e.g. if
setState
is called insidecomponentWillReceiveProps
.Update
The scope of this PR increased from its original description. Now it removes Animation priority as a feature. There's no advantage to scheduling updates with animation priority versus scheduling sync work inside
requestAnimationCallback
. So we can remove all the animation-specific code. Now there's only one type of callback.