Use unbatchedUpdates to opt-out of batching
Reverses the effect of batchedUpdates by resetting the current batching context. Does not affect nested updates, which are always deferred regardless of whether they are inside a batch.
Andrew Clark committed
Dec 30, 2016 at 11:29 UTC
ad7bcdf999ffc2cd891aef00174f245bcfb497c1
6 files changed
+90
-7
scripts/fiber/tests-passing.txt
+2
@@ -1207,6 +1207,8 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
1207
* can opt-in to deferred/animation scheduling inside componentDidMount/Update
1208
* performs Task work even after time runs out
1209
* does not perform animation work after time runs out
1210
+* can opt-out of batching using unbatchedUpdates
1211
+* nested updates are always deferred, even inside unbatchedUpdates
1212
1213
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
1214
* can update child nodes of a host instance
src/renderers/dom/fiber/ReactDOMFiber.js
+4
-4
@@ -326,8 +326,8 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
326
}
327
const newRoot = DOMRenderer.createContainer(container);
328
root = container._reactRootContainer = newRoot;
329
- // Initial mount is always sync, even if we're in a batch.
330
- DOMRenderer.syncUpdates(() => {
329
+ // Initial mount should not be batched.
330
+ DOMRenderer.unbatchedUpdates(() => {
331
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
332
});
333
} else {
@@ -354,8 +354,8 @@ var ReactDOM = {
354
unmountComponentAtNode(container : DOMContainerElement) {
355
warnAboutUnstableUse();
356
if (container._reactRootContainer) {
357
- // Unmount is always sync, even if we're in a batch.
358
- return DOMRenderer.syncUpdates(() => {
357
+ // Unmount should not be batched.
358
+ return DOMRenderer.unbatchedUpdates(() => {
359
return renderSubtreeIntoContainer(null, null, container, () => {
360
container._reactRootContainer = null;
361
});
src/renderers/noop/ReactNoop.js
+2
@@ -264,6 +264,8 @@ var ReactNoop = {
264
265
batchedUpdates: NoopRenderer.batchedUpdates,
266
267
+ unbatchedUpdates: NoopRenderer.unbatchedUpdates,
268
+
269
syncUpdates: NoopRenderer.syncUpdates,
270
271
// Logs the current state of the tree.
src/renderers/shared/fiber/ReactFiberReconciler.js
+4
@@ -82,6 +82,7 @@ export type Reconciler<C, I, TI> = {
82
/* eslint-disable no-undef */
83
// FIXME: ESLint complains about type parameter
84
batchedUpdates<A>(fn : () => A) : A,
85
+ unbatchedUpdates<A>(fn : () => A) : A,
86
syncUpdates<A>(fn : () => A) : A,
87
deferredUpdates<A>(fn : () => A) : A,
88
/* eslint-enable no-undef */
@@ -107,6 +108,7 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
108
getPriorityContext,
109
performWithPriority,
110
batchedUpdates,
111
+ unbatchedUpdates,
112
syncUpdates,
113
deferredUpdates,
114
} = ReactFiberScheduler(config);
@@ -161,6 +163,8 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
163
164
batchedUpdates,
165
166
+ unbatchedUpdates,
167
+
168
syncUpdates,
169
170
deferredUpdates,
src/renderers/shared/fiber/ReactFiberScheduler.js
+11
-3
@@ -1094,16 +1094,23 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
1094
}
1095
}
1096
1097
+ function unbatchedUpdates<A>(fn : () => A) : A {
1098
+ const previousIsBatchingUpdates = isBatchingUpdates;
1099
+ isBatchingUpdates = false;
1100
+ try {
1101
+ return fn();
1102
+ } finally {
1103
+ isBatchingUpdates = previousIsBatchingUpdates;
1104
+ }
1105
+ }
1106
+
1107
function syncUpdates<A>(fn : () => A) : A {
1108
const previousPriorityContext = priorityContext;
1099
- const previousIsBatchingUpdates = isBatchingUpdates;
1109
priorityContext = SynchronousPriority;
1101
- isBatchingUpdates = false;
1110
try {
1111
return fn();
1112
} finally {
1113
priorityContext = previousPriorityContext;
1106
- isBatchingUpdates = previousIsBatchingUpdates;
1114
}
1115
}
1116
@@ -1122,6 +1129,7 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
1129
getPriorityContext: getPriorityContext,
1130
performWithPriority: performWithPriority,
1131
batchedUpdates: batchedUpdates,
1132
+ unbatchedUpdates: unbatchedUpdates,
1133
syncUpdates: syncUpdates,
1134
deferredUpdates: deferredUpdates,
1135
};
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
+67
@@ -340,4 +340,71 @@ describe('ReactIncrementalScheduling', () => {
340
// animation priority.
341
expect(ReactNoop.getChildren()).toEqual([span(1)]);
342
});
343
+
344
+ it('can opt-out of batching using unbatchedUpdates', () => {
345
+ // syncUpdates gives synchronous priority to updates
346
+ ReactNoop.syncUpdates(() => {
347
+ // batchedUpdates downgrades sync updates to task priority
348
+ ReactNoop.batchedUpdates(() => {
349
+ ReactNoop.render(<span prop={0} />);
350
+ expect(ReactNoop.getChildren()).toEqual([]);
351
+ // Should not have flushed yet because we're still batching
352
+
353
+ // unbatchedUpdates reverses the effect of batchedUpdates, so sync
354
+ // updates are not batched
355
+ ReactNoop.unbatchedUpdates(() => {
356
+ ReactNoop.render(<span prop={1} />);
357
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
358
+ ReactNoop.render(<span prop={2} />);
359
+ expect(ReactNoop.getChildren()).toEqual([span(2)]);
360
+ });
361
+
362
+ ReactNoop.render(<span prop={3} />);
363
+ expect(ReactNoop.getChildren()).toEqual([span(2)]);
364
+ });
365
+ // Remaining update is now flushed
366
+ expect(ReactNoop.getChildren()).toEqual([span(3)]);
367
+ });
368
+ });
369
+
370
+ it('nested updates are always deferred, even inside unbatchedUpdates', () => {
371
+ let instance;
372
+ let ops = [];
373
+ class Foo extends React.Component {
374
+ state = { step: 0 };
375
+ componentDidUpdate() {
376
+ ops.push('componentDidUpdate: ' + this.state.step);
377
+ if (this.state.step === 1) {
378
+ ReactNoop.unbatchedUpdates(() => {
379
+ // This is a nested state update, so it should not be
380
+ // flushed synchronously, even though we wrapped it
381
+ // in unbatchedUpdates.
382
+ this.setState({ step: 2 });
383
+ });
384
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
385
+ }
386
+ }
387
+ render() {
388
+ ops.push('render: ' + this.state.step);
389
+ instance = this;
390
+ return <span prop={this.state.step} />;
391
+ }
392
+ }
393
+ ReactNoop.render(<Foo />);
394
+ ReactNoop.flush();
395
+ expect(ReactNoop.getChildren()).toEqual([span(0)]);
396
+
397
+ ReactNoop.syncUpdates(() => {
398
+ instance.setState({ step: 1 });
399
+ expect(ReactNoop.getChildren()).toEqual([span(2)]);
400
+ });
401
+
402
+ expect(ops).toEqual([
403
+ 'render: 0',
404
+ 'render: 1',
405
+ 'componentDidUpdate: 1',
406
+ 'render: 2',
407
+ 'componentDidUpdate: 2',
408
+ ]);
409
+ });
410
});