mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Updates at the same priority should not interrupt current render (#11578)
When we're rendering work at a specific level, and a higher priority update comes in, we interrupt the current work and restart at the higher priority. The rationale is that the high priority update is likely cheaper to render that the lower one, so it's usually worth throwing out the current work to get the high pri update on the screen as soon as possible. Currently, we also interrupt the current work if an update of *equal* priority is scheduled. The rationale here is less clear: the only reason to do this is if both updates are expected to flush at the same time, to prevent tearing. But this usually isn't the case. Separate setStates are usually distinct updates that can be flushed separately, especially if the components that are being updated are in separate subtrees. An exception is in Flux-like systems where multiple setStates are the result of a single conceptual update/event/dispatch. We can add an explicit API for batching in the future; in fact, we'd likely need one anyway to account for expiration accidentally causing consecutive updates to fall into separate buckets.
This commit is contained in:
+5
-1
@@ -455,7 +455,11 @@ var ReactNoop = {
|
||||
|
||||
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
|
||||
|
||||
flushSync: NoopRenderer.flushSync,
|
||||
flushSync(fn: () => mixed) {
|
||||
yieldedValues = [];
|
||||
NoopRenderer.flushSync(fn);
|
||||
return yieldedValues;
|
||||
},
|
||||
|
||||
// Logs the current state of the tree.
|
||||
dumpTree(rootID: string = DEFAULT_ROOT_ID) {
|
||||
|
||||
+1
-1
@@ -1202,7 +1202,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
if (
|
||||
!isWorking &&
|
||||
root === nextRoot &&
|
||||
expirationTime <= nextRenderExpirationTime
|
||||
expirationTime < nextRenderExpirationTime
|
||||
) {
|
||||
// Restart the root from the top.
|
||||
if (nextUnitOfWork !== null) {
|
||||
|
||||
@@ -168,6 +168,7 @@ describe('ReactIncremental', () => {
|
||||
ops = [];
|
||||
|
||||
// This will abort the previous work and restart
|
||||
ReactNoop.flushSync(() => ReactNoop.render(null));
|
||||
ReactNoop.render(<Foo text="baz" />);
|
||||
|
||||
// Flush part of the new work
|
||||
@@ -221,6 +222,7 @@ describe('ReactIncremental', () => {
|
||||
expect(ops).toEqual(['setState1']);
|
||||
|
||||
// This will abort the previous work and restart
|
||||
ReactNoop.flushSync(() => ReactNoop.render(<Foo />));
|
||||
inst.setState(
|
||||
() => {
|
||||
ops.push('setState2');
|
||||
@@ -1877,6 +1879,8 @@ describe('ReactIncremental', () => {
|
||||
);
|
||||
ReactNoop.flush();
|
||||
expect(ops).toEqual([
|
||||
'ShowLocale {"locale":"sv"}',
|
||||
'ShowBoth {"locale":"sv"}',
|
||||
'Intl {}',
|
||||
'ShowLocale {"locale":"en"}',
|
||||
'Router {}',
|
||||
@@ -2648,4 +2652,67 @@ describe('ReactIncremental', () => {
|
||||
'count:1, name:not brian',
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not interrupt for update at same priority', () => {
|
||||
function Parent(props) {
|
||||
ReactNoop.yield('Parent: ' + props.step);
|
||||
return <Child step={props.step} />;
|
||||
}
|
||||
|
||||
function Child(props) {
|
||||
ReactNoop.yield('Child: ' + props.step);
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent step={1} />);
|
||||
ReactNoop.flushThrough(['Parent: 1']);
|
||||
|
||||
// Interrupt at same priority
|
||||
ReactNoop.render(<Parent step={2} />);
|
||||
|
||||
expect(ReactNoop.flush()).toEqual(['Child: 1', 'Parent: 2', 'Child: 2']);
|
||||
});
|
||||
|
||||
it('does not interrupt for update at lower priority', () => {
|
||||
function Parent(props) {
|
||||
ReactNoop.yield('Parent: ' + props.step);
|
||||
return <Child step={props.step} />;
|
||||
}
|
||||
|
||||
function Child(props) {
|
||||
ReactNoop.yield('Child: ' + props.step);
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent step={1} />);
|
||||
ReactNoop.flushThrough(['Parent: 1']);
|
||||
|
||||
// Interrupt at lower priority
|
||||
ReactNoop.expire(2000);
|
||||
ReactNoop.render(<Parent step={2} />);
|
||||
|
||||
expect(ReactNoop.flush()).toEqual(['Child: 1', 'Parent: 2', 'Child: 2']);
|
||||
});
|
||||
|
||||
it('does interrupt for update at higher priority', () => {
|
||||
function Parent(props) {
|
||||
ReactNoop.yield('Parent: ' + props.step);
|
||||
return <Child step={props.step} />;
|
||||
}
|
||||
|
||||
function Child(props) {
|
||||
ReactNoop.yield('Child: ' + props.step);
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Parent step={1} />);
|
||||
ReactNoop.flushThrough(['Parent: 1']);
|
||||
|
||||
// Interrupt at higher priority
|
||||
expect(
|
||||
ReactNoop.flushSync(() => ReactNoop.render(<Parent step={2} />)),
|
||||
).toEqual(['Parent: 2', 'Child: 2']);
|
||||
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user