No nested updates

We may decide to provide this ability as an escape hatch in the future.
This commit is contained in:
Andrew Clark
2016-12-30 11:30:40 -08:00
parent 7dc5f91d88
commit b79531eefe
6 changed files with 30 additions and 120 deletions
-1
View File
@@ -1207,7 +1207,6 @@ 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 force synchronous updates with syncUpdates, even inside batchedUpdates
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
* can update child nodes of a host instance
@@ -16,7 +16,6 @@ describe('ReactPerf', () => {
var ReactDOM;
var ReactPerf;
var ReactTestUtils;
var ReactDOMFeatureFlags;
var emptyFunction;
var App;
@@ -39,7 +38,6 @@ describe('ReactPerf', () => {
ReactDOM = require('ReactDOM');
ReactPerf = require('ReactPerf');
ReactTestUtils = require('ReactTestUtils');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
emptyFunction = require('emptyFunction');
App = class extends React.Component {
@@ -647,10 +645,6 @@ describe('ReactPerf', () => {
var container = document.createElement('div');
var thrownErr = new Error('Muhaha!');
if (ReactDOMFeatureFlags.useFiber) {
spyOn(console, 'error');
}
class Evil extends React.Component {
componentWillMount() {
throw thrownErr;
@@ -685,15 +679,6 @@ describe('ReactPerf', () => {
}
ReactDOM.unmountComponentAtNode(container);
ReactPerf.stop();
if (ReactDOMFeatureFlags.useFiber) {
// A sync `render` inside cWM will print a warning. That should be the
// only warning.
expect(console.error.calls.count()).toEqual(1);
expect(console.error.calls.argsFor(0)[0]).toMatch(
/Render methods should be a pure function of props and state/
);
}
});
it('should not print errant warnings if portal throws in componentDidMount()', () => {
@@ -69,7 +69,6 @@ var {
if (__DEV__) {
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
var warning = require('warning');
}
var timeHeuristicForUnitOfWork = 1;
@@ -111,8 +110,8 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
// Keeps track of whether we're currently in a work loop.
let isPerformingWork : boolean = false;
// Keeps track of whether sync updates should be downgraded to task updates.
let shouldDeferSyncUpdates : boolean = false;
// Keeps track of whether we should should batch sync updates.
let isBatchingUpdates : boolean = false;
// The next work in progress fiber that we're currently working on.
let nextUnitOfWork : ?Fiber = null;
@@ -675,31 +674,10 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
}
function performWork(priorityLevel : PriorityLevel, deadline : Deadline | null) {
// If performWork is called recursively, we need to save the previous state
// of the scheduler so it can be restored before the function exits.
// Recursion is only possible when using syncUpdates.
const previousPriorityContext = priorityContext;
const previousPriorityContextBeforeReconciliation = priorityContextBeforeReconciliation;
const previousIsPerformingWork = isPerformingWork;
const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
const previousNextEffect = nextEffect;
const previousCommitPhaseBoundaries = commitPhaseBoundaries;
const previousFirstUncaughtError = firstUncaughtError;
const previousFatalError = fatalError;
const previousIsCommitting = isCommitting;
const previousIsUnmounting = isUnmounting;
priorityContext = NoWork;
priorityContextBeforeReconciliation = NoWork;
if (isPerformingWork) {
throw new Error('performWork was called recursively.');
}
isPerformingWork = true;
shouldDeferSyncUpdates = true;
nextEffect = null;
commitPhaseBoundaries = null;
firstUncaughtError = null;
fatalError = null;
isCommitting = false;
isUnmounting = false;
const isPerformingDeferredWork = Boolean(deadline);
let deadlineHasExpired = false;
@@ -805,17 +783,12 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
const errorToThrow = fatalError || firstUncaughtError;
// We're done performing work. Restore the previous state of the scheduler.
priorityContext = previousPriorityContext;
priorityContextBeforeReconciliation = previousPriorityContextBeforeReconciliation;
isPerformingWork = previousIsPerformingWork;
shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
nextEffect = previousNextEffect;
commitPhaseBoundaries = previousCommitPhaseBoundaries;
firstUncaughtError = previousFirstUncaughtError;
fatalError = previousFatalError;
isCommitting = previousIsCommitting;
isUnmounting = previousIsUnmounting;
// We're done performing work. Time to clean up.
isPerformingWork = false;
fatalError = null;
firstUncaughtError = null;
capturedErrors = null;
failedBoundaries = null;
// It's safe to throw any unhandled errors.
if (errorToThrow) {
@@ -1030,20 +1003,6 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
}
function scheduleUpdate(fiber : Fiber, priorityLevel : PriorityLevel) {
// Detect if a synchronous update is made during render (or begin phase).
if (priorityLevel === SynchronousPriority && isPerformingWork && !isCommitting) {
if (__DEV__) {
warning(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.'
);
}
// Downgrade to Task priority to prevent an infinite loop.
priorityLevel = TaskPriority;
}
let node = fiber;
let shouldContinue = true;
while (node && shouldContinue) {
@@ -1098,8 +1057,9 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
}
function getPriorityContext() : PriorityLevel {
// If we're in a batch, downgrade sync priority to task priority
if (priorityContext === SynchronousPriority && shouldDeferSyncUpdates) {
// If we're in a batch, or if we're already performing work, downgrade sync
// priority to task priority
if (priorityContext === SynchronousPriority && (isPerformingWork || isBatchingUpdates)) {
return TaskPriority;
}
return priorityContext;
@@ -1120,15 +1080,15 @@ module.exports = function<T, P, I, TI, C, CX, CI>(config : HostConfig<T, P, I, T
}
function batchedUpdates<A, R>(fn : (a: A) => R, a : A) : R {
const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
shouldDeferSyncUpdates = true;
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a);
} finally {
shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
// If we're not already inside a batch, we need to flush any task work
// that was created by the user-provided function.
if (!shouldDeferSyncUpdates) {
if (!isPerformingWork && !isBatchingUpdates) {
performWork(TaskPriority);
}
}
@@ -1136,14 +1096,14 @@ 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;
const previousShouldDeferSyncUpdates = shouldDeferSyncUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
priorityContext = SynchronousPriority;
shouldDeferSyncUpdates = false;
isBatchingUpdates = false;
try {
return fn();
} finally {
priorityContext = previousPriorityContext;
shouldDeferSyncUpdates = previousShouldDeferSyncUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
}
}
@@ -340,14 +340,4 @@ describe('ReactIncrementalScheduling', () => {
// animation priority.
expect(ReactNoop.getChildren()).toEqual([span(1)]);
});
it('can force synchronous updates with syncUpdates, even inside batchedUpdates', done => {
ReactNoop.batchedUpdates(() => {
ReactNoop.syncUpdates(() => {
ReactNoop.render(<span />);
expect(ReactNoop.getChildren()).toEqual([span()]);
done();
});
});
});
});
@@ -18,7 +18,6 @@ describe('ReactComponentTreeHook', () => {
var ReactInstanceMap;
var ReactComponentTreeHook;
var ReactComponentTreeTestUtils;
var ReactDOMFeatureFlags;
beforeEach(() => {
jest.resetModules();
@@ -29,7 +28,6 @@ describe('ReactComponentTreeHook', () => {
ReactInstanceMap = require('ReactInstanceMap');
ReactComponentTreeHook = require('ReactComponentTreeHook');
ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
});
function assertTreeMatches(pairs) {
@@ -1843,9 +1841,6 @@ describe('ReactComponentTreeHook', () => {
// https://github.com/facebook/react/issues/7187
var el = document.createElement('div');
var portalEl = document.createElement('div');
if (ReactDOMFeatureFlags.useFiber) {
spyOn(console, 'error');
}
class Foo extends React.Component {
componentWillMount() {
ReactDOM.render(<div />, portalEl);
@@ -1855,14 +1850,6 @@ describe('ReactComponentTreeHook', () => {
}
}
ReactDOM.render(<Foo />, el);
if (ReactDOMFeatureFlags.useFiber) {
// A sync `render` inside cWM will print a warning. That should be the
// only warning.
expect(console.error.calls.count()).toEqual(1);
expect(console.error.calls.argsFor(0)[0]).toMatch(
/Render methods should be a pure function of props and state/
);
}
});
it('is created when calling renderToString during render', () => {
@@ -1143,25 +1143,14 @@ describe('ReactUpdates', () => {
expect(mounts).toBe(1);
});
it('mounts and unmounts are sync even in a batch', () => {
var container1 = document.createElement('div');
var container2 = document.createElement('div');
let called = false;
class Foo extends React.Component {
componentDidMount() {
called = true;
ReactDOM.render(<div>Hello</div>, container2);
expect(container2.textContent).toEqual('Hello');
ReactDOM.unmountComponentAtNode(container2);
expect(container2.textContent).toEqual('');
}
render() {
return <div>{this.props.step}</div>;
}
}
ReactDOM.render(<Foo />, container1);
expect(called).toEqual(true);
it('mounts and unmounts are sync even in a batch', done => {
var container = document.createElement('div');
ReactDOM.unstable_batchedUpdates(() => {
ReactDOM.render(<div>Hello</div>, container);
expect(container.textContent).toEqual('Hello');
ReactDOM.unmountComponentAtNode(container);
expect(container.textContent).toEqual('');
done();
});
});
});