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.
This commit is contained in:
Andrew Clark
2017-01-03 10:24:27 -08:00
parent b79531eefe
commit ad7bcdf999
6 changed files with 91 additions and 8 deletions
+2
View File
@@ -1207,6 +1207,8 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
* can opt-in to deferred/animation scheduling inside componentDidMount/Update
* performs Task work even after time runs out
* does not perform animation work after time runs out
* can opt-out of batching using unbatchedUpdates
* nested updates are always deferred, even inside unbatchedUpdates
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* can update child nodes of a host instance
+4 -4
View File
@@ -326,8 +326,8 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
}
const newRoot = DOMRenderer.createContainer(container);
root = container._reactRootContainer = newRoot;
// Initial mount is always sync, even if we're in a batch.
DOMRenderer.syncUpdates(() => {
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
});
} else {
@@ -354,8 +354,8 @@ var ReactDOM = {
unmountComponentAtNode(container : DOMContainerElement) {
warnAboutUnstableUse();
if (container._reactRootContainer) {
// Unmount is always sync, even if we're in a batch.
return DOMRenderer.syncUpdates(() => {
// Unmount should not be batched.
return DOMRenderer.unbatchedUpdates(() => {
return renderSubtreeIntoContainer(null, null, container, () => {
container._reactRootContainer = null;
});
+2
View File
@@ -264,6 +264,8 @@ var ReactNoop = {
batchedUpdates: NoopRenderer.batchedUpdates,
unbatchedUpdates: NoopRenderer.unbatchedUpdates,
syncUpdates: NoopRenderer.syncUpdates,
// Logs the current state of the tree.
@@ -82,6 +82,7 @@ export type Reconciler<C, I, TI> = {
/* eslint-disable no-undef */
// FIXME: ESLint complains about type parameter
batchedUpdates<A>(fn : () => A) : A,
unbatchedUpdates<A>(fn : () => A) : A,
syncUpdates<A>(fn : () => A) : A,
deferredUpdates<A>(fn : () => A) : A,
/* eslint-enable no-undef */
@@ -107,6 +108,7 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
getPriorityContext,
performWithPriority,
batchedUpdates,
unbatchedUpdates,
syncUpdates,
deferredUpdates,
} = ReactFiberScheduler(config);
@@ -161,6 +163,8 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
batchedUpdates,
unbatchedUpdates,
syncUpdates,
deferredUpdates,
@@ -1094,19 +1094,26 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
}
}
function syncUpdates<A>(fn : () => A) : A {
const previousPriorityContext = priorityContext;
function unbatchedUpdates<A>(fn : () => A) : A {
const previousIsBatchingUpdates = isBatchingUpdates;
priorityContext = SynchronousPriority;
isBatchingUpdates = false;
try {
return fn();
} finally {
priorityContext = previousPriorityContext;
isBatchingUpdates = previousIsBatchingUpdates;
}
}
function syncUpdates<A>(fn : () => A) : A {
const previousPriorityContext = priorityContext;
priorityContext = SynchronousPriority;
try {
return fn();
} finally {
priorityContext = previousPriorityContext;
}
}
function deferredUpdates<A>(fn : () => A) : A {
const previousPriorityContext = priorityContext;
priorityContext = LowPriority;
@@ -1122,6 +1129,7 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
getPriorityContext: getPriorityContext,
performWithPriority: performWithPriority,
batchedUpdates: batchedUpdates,
unbatchedUpdates: unbatchedUpdates,
syncUpdates: syncUpdates,
deferredUpdates: deferredUpdates,
};
@@ -340,4 +340,71 @@ describe('ReactIncrementalScheduling', () => {
// animation priority.
expect(ReactNoop.getChildren()).toEqual([span(1)]);
});
it('can opt-out of batching using unbatchedUpdates', () => {
// syncUpdates gives synchronous priority to updates
ReactNoop.syncUpdates(() => {
// batchedUpdates downgrades sync updates to task priority
ReactNoop.batchedUpdates(() => {
ReactNoop.render(<span prop={0} />);
expect(ReactNoop.getChildren()).toEqual([]);
// Should not have flushed yet because we're still batching
// unbatchedUpdates reverses the effect of batchedUpdates, so sync
// updates are not batched
ReactNoop.unbatchedUpdates(() => {
ReactNoop.render(<span prop={1} />);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.render(<span prop={2} />);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
ReactNoop.render(<span prop={3} />);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
// Remaining update is now flushed
expect(ReactNoop.getChildren()).toEqual([span(3)]);
});
});
it('nested updates are always deferred, even inside unbatchedUpdates', () => {
let instance;
let ops = [];
class Foo extends React.Component {
state = { step: 0 };
componentDidUpdate() {
ops.push('componentDidUpdate: ' + this.state.step);
if (this.state.step === 1) {
ReactNoop.unbatchedUpdates(() => {
// This is a nested state update, so it should not be
// flushed synchronously, even though we wrapped it
// in unbatchedUpdates.
this.setState({ step: 2 });
});
expect(ReactNoop.getChildren()).toEqual([span(1)]);
}
}
render() {
ops.push('render: ' + this.state.step);
instance = this;
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(0)]);
ReactNoop.syncUpdates(() => {
instance.setState({ step: 1 });
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
expect(ops).toEqual([
'render: 0',
'render: 1',
'componentDidUpdate: 1',
'render: 2',
'componentDidUpdate: 2',
]);
});
});