@samitouri / QOS-React / commits / ad7bcdf999

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 });