Skip to content

Commit 2e7eac6

Browse files
committed
Committing a update on one root should not flush work on another root
`forceExpire` is tracked per root rather than per scheduler.
1 parent 21eb6bf commit 2e7eac6

File tree

4 files changed

+47
-13
lines changed

4 files changed

+47
-13
lines changed

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
344344
return;
345345
}
346346
processUpdateQueue(blockers, null, null, null, expirationTime);
347-
expireWork(expirationTime);
347+
expireWork(root, expirationTime);
348348
};
349349
WorkNode.prototype.then = function(callback) {
350350
const root = this._reactRootContainer;

src/renderers/shared/fiber/ReactFiberRoot.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export type FiberRoot = {
3636
// A queue of callbacks that fire once their corresponding expiration time
3737
// has completed. Only fired once.
3838
completionCallbacks: UpdateQueue<null> | null,
39+
// When set, indicates that all work in this tree with this time or earlier
40+
// should be flushed by the end of the batch, as if it has task priority.
41+
forceExpire: null | ExpirationTime,
3942
// The work schedule is a linked list.
4043
nextScheduledRoot: FiberRoot | null,
4144
// Top context object, used by renderSubtreeIntoContainer
@@ -66,6 +69,7 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
6669
completedAt: Done,
6770
blockers: null,
6871
completionCallbacks: null,
72+
forceExpire: null,
6973
nextScheduledRoot: null,
7074
context: null,
7175
pendingContext: null,

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
225225
let nextUnitOfWork: Fiber | null = null;
226226
// The time at which we're currently rendering work.
227227
let nextRenderExpirationTime: ExpirationTime = Done;
228-
// If not null, all work up to and including this time should be
229-
// flushed before the end of the current batch.
230-
let forceExpire: ExpirationTime | null = null;
231228

232229
// The next fiber with an effect that we're currently committing.
233230
let nextEffect: Fiber | null = null;
@@ -307,7 +304,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
307304
(earliestExpirationTime === Done ||
308305
earliestExpirationTime > rootExpirationTime)
309306
) {
310-
earliestExpirationTime = root.current.expirationTime;
307+
earliestExpirationTime = rootExpirationTime;
311308
earliestExpirationRoot = root;
312309
}
313310
// We didn't find anything to do in this root, so let's try the next one.
@@ -1717,25 +1714,30 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
17171714
}
17181715

17191716
function recalculateCurrentTime(): ExpirationTime {
1720-
if (forceExpire !== null) {
1721-
return forceExpire;
1717+
if (nextRenderedTree !== null) {
1718+
// Check if the current root is being force expired.
1719+
const forceExpire = nextRenderedTree.forceExpire;
1720+
if (forceExpire !== null) {
1721+
// Override the current time with the `forceExpire` time. This has the
1722+
// effect of expiring all work up to and including that time.
1723+
mostRecentCurrentTime = forceExpire;
1724+
return forceExpire;
1725+
}
17221726
}
17231727
mostRecentCurrentTime = msToExpirationTime(now());
17241728
return mostRecentCurrentTime;
17251729
}
17261730

1727-
function expireWork(expirationTime: ExpirationTime): void {
1731+
function expireWork(root: FiberRoot, expirationTime: ExpirationTime): void {
17281732
invariant(
17291733
!isPerformingWork,
17301734
'Cannot commit while already performing work.',
17311735
);
1732-
// Override the current time with the given time. This has the effect of
1733-
// expiring all work up to and including that time.
1734-
forceExpire = mostRecentCurrentTime = expirationTime;
1736+
root.forceExpire = expirationTime;
17351737
try {
17361738
performWork(TaskPriority, null);
17371739
} finally {
1738-
forceExpire = null;
1740+
root.forceExpire = null;
17391741
recalculateCurrentTime();
17401742
}
17411743
}

src/renderers/shared/fiber/__tests__/ReactIncrementalRoot-test.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('ReactIncrementalRoot', () => {
135135
expect(root.getChildren()).toEqual([span('A')]);
136136
});
137137

138-
it('flushes ealier work if later work is committed', () => {
138+
it('flushes earlier work if later work is committed', () => {
139139
let ops = [];
140140
const root = ReactNoop.createRoot();
141141
const work1 = root.prerender(<span prop="A" />);
@@ -153,4 +153,32 @@ describe('ReactIncrementalRoot', () => {
153153
expect(root.getChildren()).toEqual([span('B')]);
154154
expect(ops).toEqual(['complete 1', 'complete 2']);
155155
});
156+
157+
it('committing work on one tree does not commit or expire work in a separate tree', () => {
158+
let ops = [];
159+
160+
const rootA = ReactNoop.createRoot('A');
161+
const rootB = ReactNoop.createRoot('B');
162+
163+
function Foo(props) {
164+
ops.push(props.label);
165+
return <span prop={props.label} />;
166+
}
167+
168+
// Prerender work on two separate roots
169+
const workA = rootA.prerender(<Foo label="A" />);
170+
rootB.prerender(<Foo label="B" />);
171+
172+
expect(rootA.getChildren()).toEqual([]);
173+
expect(rootB.getChildren()).toEqual([]);
174+
expect(ops).toEqual([]);
175+
176+
// Commit root A. This forces the remaining work on root A to expire, but
177+
// should not expire work on root B.
178+
workA.commit();
179+
180+
expect(rootA.getChildren()).toEqual([span('A')]);
181+
expect(rootB.getChildren()).toEqual([]);
182+
expect(ops).toEqual(['A']);
183+
});
156184
});

0 commit comments

Comments
 (0)