mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Priority context during reconciliation
setState inside render/cWRP should have the same priority as whatever level is currently being reconciled.
This commit is contained in:
@@ -1237,6 +1237,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalUpdates-test.js
|
||||
* can abort an update, schedule additional updates, and resume
|
||||
* can abort an update, schedule a replaceState, and resume
|
||||
* does not call callbacks that are scheduled by another callback until a later commit
|
||||
* gives setState during reconciliation the same priority as whatever level is currently reconciling
|
||||
* enqueues setState inside an updater function as if the in-progress update is progressed (and warns)
|
||||
|
||||
src/renderers/shared/fiber/__tests__/ReactTopLevelFragment-test.js
|
||||
|
||||
@@ -71,8 +71,10 @@ if (__DEV__) {
|
||||
module.exports = function<T, P, I, TI, C, CX>(
|
||||
config : HostConfig<T, P, I, TI, C, CX>,
|
||||
hostContext : HostContext<C, CX>,
|
||||
scheduleUpdateAtPriority : (fiber: Fiber, priorityLevel : PriorityLevel) => void,
|
||||
getPriorityContext : () => PriorityLevel
|
||||
scheduleSetState: (fiber : Fiber, partialState : any) => void,
|
||||
scheduleReplaceState: (fiber : Fiber, state : any) => void,
|
||||
scheduleForceUpdate: (fiber : Fiber) => void,
|
||||
scheduleUpdateCallback: (fiber : Fiber, callback : Function) => void,
|
||||
) {
|
||||
|
||||
const { shouldSetTextContent } = config;
|
||||
@@ -89,7 +91,12 @@ module.exports = function<T, P, I, TI, C, CX>(
|
||||
mountClassInstance,
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
} = ReactFiberClassComponent(scheduleUpdateAtPriority, getPriorityContext);
|
||||
} = ReactFiberClassComponent(
|
||||
scheduleSetState,
|
||||
scheduleReplaceState,
|
||||
scheduleForceUpdate,
|
||||
scheduleUpdateCallback
|
||||
);
|
||||
|
||||
function markChildAsProgressed(current, workInProgress, priorityLevel) {
|
||||
// We now have clones. Let's store them as the currently progressed work.
|
||||
|
||||
@@ -19,10 +19,6 @@ var {
|
||||
getMaskedContext,
|
||||
} = require('ReactFiberContext');
|
||||
var {
|
||||
addUpdate,
|
||||
addReplaceUpdate,
|
||||
addForceUpdate,
|
||||
addCallback,
|
||||
beginUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var { hasContextChanged } = require('ReactFiberContext');
|
||||
@@ -36,8 +32,10 @@ var invariant = require('invariant');
|
||||
const isArray = Array.isArray;
|
||||
|
||||
module.exports = function(
|
||||
scheduleUpdateAtPriority : (fiber: Fiber, priorityLevel : PriorityLevel) => void,
|
||||
getPriorityContext : () => PriorityLevel,
|
||||
scheduleSetState: (fiber : Fiber, partialState : any) => void,
|
||||
scheduleReplaceState: (fiber : Fiber, state : any) => void,
|
||||
scheduleForceUpdate: (fiber : Fiber) => void,
|
||||
scheduleUpdateCallback: (fiber : Fiber, callback : Function) => void,
|
||||
) {
|
||||
|
||||
// Class component state updater
|
||||
@@ -45,27 +43,19 @@ module.exports = function(
|
||||
isMounted,
|
||||
enqueueSetState(instance, partialState) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext();
|
||||
addUpdate(fiber, partialState, priorityLevel);
|
||||
scheduleUpdateAtPriority(fiber, priorityLevel);
|
||||
scheduleSetState(fiber, partialState);
|
||||
},
|
||||
enqueueReplaceState(instance, state) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext();
|
||||
addReplaceUpdate(fiber, state, priorityLevel);
|
||||
scheduleUpdateAtPriority(fiber, priorityLevel);
|
||||
scheduleReplaceState(fiber, state);
|
||||
},
|
||||
enqueueForceUpdate(instance) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext();
|
||||
addForceUpdate(fiber, priorityLevel);
|
||||
scheduleUpdateAtPriority(fiber, priorityLevel);
|
||||
scheduleForceUpdate(fiber);
|
||||
},
|
||||
enqueueCallback(instance, callback) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const priorityLevel = getPriorityContext();
|
||||
addCallback(fiber, callback, priorityLevel);
|
||||
scheduleUpdateAtPriority(fiber, priorityLevel);
|
||||
scheduleUpdateCallback(fiber, callback);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ var {
|
||||
var { createFiberRoot } = require('ReactFiberRoot');
|
||||
var ReactFiberScheduler = require('ReactFiberScheduler');
|
||||
|
||||
var { addCallback } = require('ReactFiberUpdateQueue');
|
||||
|
||||
if (__DEV__) {
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
}
|
||||
@@ -100,7 +98,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
|
||||
var {
|
||||
scheduleWork,
|
||||
getPriorityContext,
|
||||
scheduleUpdateCallback,
|
||||
performWithPriority,
|
||||
batchedUpdates,
|
||||
syncUpdates,
|
||||
@@ -123,8 +121,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
current.alternate.pendingProps = element;
|
||||
}
|
||||
if (callback) {
|
||||
const priorityLevel = getPriorityContext();
|
||||
addCallback(current, callback, priorityLevel);
|
||||
scheduleUpdateCallback(current, callback);
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
@@ -154,8 +151,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
current.alternate.pendingProps = element;
|
||||
}
|
||||
if (callback) {
|
||||
const priorityLevel = getPriorityContext();
|
||||
addCallback(current, callback, priorityLevel);
|
||||
scheduleUpdateCallback(current, callback);
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
@@ -56,6 +56,10 @@ var {
|
||||
|
||||
var {
|
||||
getPendingPriority,
|
||||
addUpdate,
|
||||
addReplaceUpdate,
|
||||
addForceUpdate,
|
||||
addCallback,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
|
||||
var {
|
||||
@@ -72,8 +76,14 @@ var timeHeuristicForUnitOfWork = 1;
|
||||
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
|
||||
const hostContext = ReactFiberHostContext(config);
|
||||
const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
|
||||
const { beginWork, beginFailedWork } =
|
||||
ReactFiberBeginWork(config, hostContext, scheduleUpdateAtPriority, getPriorityContext);
|
||||
const { beginWork, beginFailedWork } = ReactFiberBeginWork(
|
||||
config,
|
||||
hostContext,
|
||||
scheduleSetState,
|
||||
scheduleReplaceState,
|
||||
scheduleForceUpdate,
|
||||
scheduleUpdateCallback,
|
||||
);
|
||||
const { completeWork } = ReactFiberCompleteWork(config, hostContext);
|
||||
const {
|
||||
commitPlacement,
|
||||
@@ -95,6 +105,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
SynchronousPriority :
|
||||
LowPriority;
|
||||
|
||||
// Keep track of this so we can reset the priority context if an error
|
||||
// is thrown during reconciliation.
|
||||
let priorityContextBeforeReconciliation : PriorityLevel = NoWork;
|
||||
|
||||
// Keeps track of whether we're currently in a work loop. Used to batch
|
||||
// nested updates.
|
||||
let isPerformingWork : boolean = false;
|
||||
@@ -145,15 +159,6 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
}
|
||||
}
|
||||
|
||||
function getPriorityContext() : PriorityLevel {
|
||||
let priorityLevel = priorityContext;
|
||||
// If we're in a batch, downgrade sync priority to task priority
|
||||
if (priorityLevel === SynchronousPriority && isPerformingWork) {
|
||||
priorityLevel = TaskPriority;
|
||||
}
|
||||
return priorityLevel;
|
||||
}
|
||||
|
||||
function findNextUnitOfWork() {
|
||||
// Clear out roots with no more work on them, or if they have uncaught errors
|
||||
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
|
||||
@@ -481,6 +486,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
}
|
||||
|
||||
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
@@ -491,7 +497,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
ReactFiberInstrumentation.debugTool.onWillBeginWork(workInProgress);
|
||||
}
|
||||
// See if beginning this work spawns more work.
|
||||
|
||||
priorityContextBeforeReconciliation = priorityContext;
|
||||
priorityContext = nextPriorityLevel;
|
||||
let next = beginWork(current, workInProgress, nextPriorityLevel);
|
||||
priorityContext = priorityContextBeforeReconciliation;
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
|
||||
@@ -513,10 +523,13 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
ReactDebugCurrentFiber.current = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function performFailedUnitOfWork(workInProgress : Fiber) : ?Fiber {
|
||||
|
||||
// The current, flushed, state of this fiber is the alternate.
|
||||
// Ideally nothing should rely on this, but relying on it here
|
||||
// means that we don't need an additional field on the work in
|
||||
@@ -527,7 +540,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
ReactFiberInstrumentation.debugTool.onWillBeginWork(workInProgress);
|
||||
}
|
||||
// See if beginning this work spawns more work.
|
||||
priorityContextBeforeReconciliation = priorityContext;
|
||||
priorityContext = nextPriorityLevel;
|
||||
let next = beginFailedWork(current, workInProgress, nextPriorityLevel);
|
||||
priorityContext = priorityContextBeforeReconciliation;
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
|
||||
@@ -686,6 +702,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
// We caught an error during either the begin or complete phases.
|
||||
const failedWork = nextUnitOfWork;
|
||||
|
||||
// Reset the priority context to its value before reconcilation.
|
||||
priorityContext = priorityContextBeforeReconciliation;
|
||||
|
||||
// "Capture" the error by finding the nearest boundary. If there is no
|
||||
// error boundary, the nearest host container acts as one. If
|
||||
// captureError returns null, the error was intentionally ignored.
|
||||
@@ -1002,6 +1021,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
}
|
||||
|
||||
function scheduleUpdateAtPriority(fiber : Fiber, priorityLevel : PriorityLevel) {
|
||||
// If we're in a batch, downgrade sync priority to task priority
|
||||
if (priorityLevel === SynchronousPriority && isPerformingWork) {
|
||||
priorityLevel = TaskPriority;
|
||||
}
|
||||
|
||||
let node = fiber;
|
||||
let shouldContinue = true;
|
||||
while (node && shouldContinue) {
|
||||
@@ -1040,6 +1064,26 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
scheduleUpdateAtPriority(fiber, TaskPriority);
|
||||
}
|
||||
|
||||
function scheduleSetState(fiber : Fiber, partialState : any) {
|
||||
addUpdate(fiber, partialState, priorityContext);
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
function scheduleReplaceState(fiber : Fiber, state : any) {
|
||||
addReplaceUpdate(fiber, state, priorityContext);
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
function scheduleForceUpdate(fiber : Fiber) {
|
||||
addForceUpdate(fiber, priorityContext);
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
function scheduleUpdateCallback(fiber : Fiber, callback : Function) {
|
||||
addCallback(fiber, callback, priorityContext);
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
|
||||
const previousPriorityContext = priorityContext;
|
||||
priorityContext = priorityLevel;
|
||||
@@ -1088,7 +1132,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
|
||||
return {
|
||||
scheduleWork: scheduleWork,
|
||||
getPriorityContext: getPriorityContext,
|
||||
scheduleUpdateCallback: scheduleUpdateCallback,
|
||||
performWithPriority: performWithPriority,
|
||||
batchedUpdates: batchedUpdates,
|
||||
syncUpdates: syncUpdates,
|
||||
|
||||
@@ -246,22 +246,47 @@ describe('ReactIncrementalUpdates', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
|
||||
let instance;
|
||||
let ops = [];
|
||||
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentWillReceiveProps() {
|
||||
ops.push('componentWillReceiveProps');
|
||||
this.setState({ b: 'b' });
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
return <span prop={Object.keys(this.state).join('')} />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState({ a: 'a' });
|
||||
ReactNoop.render(<Foo />); // Trigger componentWillReceiveProps
|
||||
});
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([span('ab')]);
|
||||
expect(ops).toEqual([
|
||||
'componentWillReceiveProps',
|
||||
'render',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
|
||||
spyOn(console, 'error');
|
||||
let instance;
|
||||
let ops = [];
|
||||
class Foo extends React.Component {
|
||||
state = {};
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount');
|
||||
this.setState(function a() {
|
||||
// Force update b to have Task priority
|
||||
ReactNoop.syncUpdates(() => {
|
||||
this.setState({ b: 'b' });
|
||||
});
|
||||
return { a: 'a' };
|
||||
});
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
instance = this;
|
||||
@@ -271,41 +296,25 @@ describe('ReactIncrementalUpdates', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
|
||||
instance.setState(function a() {
|
||||
ops.push('setState updater');
|
||||
this.setState({ b: 'b' });
|
||||
return { a: 'a' };
|
||||
});
|
||||
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('ab')]);
|
||||
expect(ops).toEqual([
|
||||
// Initial render
|
||||
'render',
|
||||
'componentDidMount',
|
||||
// Updates a and b both have Task priority. Update b is enqueued while
|
||||
// update a is being processed, but it should be inserted into the queue
|
||||
// as if update a is already processed. Then processing continues. Because
|
||||
// they have the same priority, update b is processed in the same batch.
|
||||
// So there should only be a single render below.
|
||||
'setState updater',
|
||||
// Update b is enqueued with the same priority as update a, so it should
|
||||
// be flushed in the same commit.
|
||||
'render',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.performAnimationWork(() => {
|
||||
instance.setState(function c() {
|
||||
// Update d happens during the begin phase, so it has low priority.
|
||||
this.setState({ d: 'd' });
|
||||
return { c: 'c' };
|
||||
});
|
||||
});
|
||||
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('abcd')]);
|
||||
expect(ops).toEqual([
|
||||
// Update c has animation priority. Update d is enqueued while c is being
|
||||
// processed with animation priority. Because d is low priority, it is not
|
||||
// processed until the next render. So there should be two renders below.
|
||||
'render',
|
||||
'render',
|
||||
]);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
console.error.calls.reset();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user