mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Coalesce like-priority updates made to the same component
Maybe it's ok to do this across components? It would make this much simpler.
This commit is contained in:
+8
-5
@@ -14,6 +14,7 @@ import type {HostContext} from './ReactFiberHostContext';
|
||||
import type {HydrationContext} from './ReactFiberHydrationContext';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
@@ -92,11 +93,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
startTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
) => void,
|
||||
computeExpirationForFiber: (
|
||||
startTime: ExpirationTime,
|
||||
fiber: Fiber,
|
||||
) => ExpirationTime,
|
||||
computeUpdatePriorityForFiber: (fiber: Fiber) => PriorityLevel,
|
||||
recalculateCurrentTime: () => ExpirationTime,
|
||||
computeExpirationTimeForPriority: (
|
||||
priorityLevel: PriorityLevel,
|
||||
startTime: ExpirationTime,
|
||||
) => ExpirationTime,
|
||||
) {
|
||||
const {shouldSetTextContent, shouldDeprioritizeSubtree} = config;
|
||||
|
||||
@@ -117,10 +119,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
updateClassInstance,
|
||||
} = ReactFiberClassComponent(
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
computeUpdatePriorityForFiber,
|
||||
memoizeProps,
|
||||
memoizeState,
|
||||
recalculateCurrentTime,
|
||||
computeExpirationTimeForPriority,
|
||||
);
|
||||
|
||||
// TODO: Remove this and use reconcileChildrenAtExpirationTime directly.
|
||||
|
||||
+24
-7
@@ -10,6 +10,7 @@
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {CapturedValue} from './ReactCapturedValue';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
|
||||
import {Update} from 'shared/ReactTypeOfSideEffect';
|
||||
import {
|
||||
@@ -115,13 +116,14 @@ export default function(
|
||||
startTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
) => void,
|
||||
computeExpirationForFiber: (
|
||||
startTime: ExpirationTime,
|
||||
fiber: Fiber,
|
||||
) => ExpirationTime,
|
||||
computeUpdatePriorityForFiber: (fiber: Fiber) => PriorityLevel,
|
||||
memoizeProps: (workInProgress: Fiber, props: any) => void,
|
||||
memoizeState: (workInProgress: Fiber, state: any) => void,
|
||||
recalculateCurrentTime: () => ExpirationTime,
|
||||
computeExpirationTimeForPriority: (
|
||||
priorityLevel: PriorityLevel,
|
||||
startTime: ExpirationTime,
|
||||
) => ExpirationTime,
|
||||
) {
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
@@ -132,10 +134,15 @@ export default function(
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'setState');
|
||||
}
|
||||
const priorityLevel = computeUpdatePriorityForFiber(fiber);
|
||||
const currentTime = recalculateCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, fiber);
|
||||
const expirationTime = computeExpirationTimeForPriority(
|
||||
priorityLevel,
|
||||
currentTime,
|
||||
);
|
||||
const update = {
|
||||
expirationTime,
|
||||
priorityLevel,
|
||||
partialState,
|
||||
callback,
|
||||
isReplace: false,
|
||||
@@ -152,10 +159,15 @@ export default function(
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'replaceState');
|
||||
}
|
||||
const priorityLevel = computeUpdatePriorityForFiber(fiber);
|
||||
const currentTime = recalculateCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, fiber);
|
||||
const expirationTime = computeExpirationTimeForPriority(
|
||||
priorityLevel,
|
||||
currentTime,
|
||||
);
|
||||
const update = {
|
||||
expirationTime,
|
||||
priorityLevel,
|
||||
partialState: state,
|
||||
callback,
|
||||
isReplace: true,
|
||||
@@ -172,11 +184,16 @@ export default function(
|
||||
if (__DEV__) {
|
||||
warnOnInvalidCallback(callback, 'forceUpdate');
|
||||
}
|
||||
const priorityLevel = computeUpdatePriorityForFiber(fiber);
|
||||
const currentTime = recalculateCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, fiber);
|
||||
const expirationTime = computeExpirationTimeForPriority(
|
||||
priorityLevel,
|
||||
currentTime,
|
||||
);
|
||||
const update = {
|
||||
expirationTime,
|
||||
partialState: null,
|
||||
priorityLevel,
|
||||
callback,
|
||||
isReplace: false,
|
||||
isForced: true,
|
||||
|
||||
+12
-5
@@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber';
|
||||
import type {FiberRoot} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
|
||||
import {
|
||||
enableMutatingReconciler,
|
||||
@@ -92,12 +93,13 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
startTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
) => void,
|
||||
computeExpirationForFiber: (
|
||||
startTime: ExpirationTime,
|
||||
fiber: Fiber,
|
||||
) => ExpirationTime,
|
||||
computeUpdatePriorityForFiber: (fiber: Fiber) => PriorityLevel,
|
||||
markLegacyErrorBoundaryAsFailed: (instance: mixed) => void,
|
||||
recalculateCurrentTime: () => ExpirationTime,
|
||||
computeExpirationTimeForPriority: (
|
||||
priorityLevel: PriorityLevel,
|
||||
startTime: ExpirationTime,
|
||||
) => ExpirationTime,
|
||||
) {
|
||||
const {getPublicInstance, mutation, persistence} = config;
|
||||
|
||||
@@ -156,10 +158,15 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
|
||||
function scheduleExpirationBoundaryRecovery(fiber) {
|
||||
const priorityLevel = computeUpdatePriorityForFiber(fiber);
|
||||
const currentTime = recalculateCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, fiber);
|
||||
const expirationTime = computeExpirationTimeForPriority(
|
||||
priorityLevel,
|
||||
currentTime,
|
||||
);
|
||||
const update = {
|
||||
expirationTime,
|
||||
priorityLevel,
|
||||
partialState: false,
|
||||
callback: null,
|
||||
isReplace: true,
|
||||
|
||||
+41
-43
@@ -11,6 +11,7 @@ import type {Fiber} from './ReactFiber';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
|
||||
import {
|
||||
findCurrentHostFiber,
|
||||
@@ -33,6 +34,7 @@ import ReactFiberScheduler from './ReactFiberScheduler';
|
||||
import {insertUpdateIntoFiber} from './ReactFiberUpdateQueue';
|
||||
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
|
||||
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
|
||||
import {NoPriority} from './ReactPriorityLevel';
|
||||
|
||||
let didWarnAboutNestedUpdates;
|
||||
|
||||
@@ -296,7 +298,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
const {
|
||||
computeUniqueAsyncExpiration,
|
||||
recalculateCurrentTime,
|
||||
computeExpirationForFiber,
|
||||
computeUpdatePriorityForFiber,
|
||||
computeExpirationTimeForPriority,
|
||||
scheduleWork,
|
||||
requestWork,
|
||||
flushRoot,
|
||||
@@ -310,13 +313,37 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
flushInteractiveUpdates,
|
||||
} = ReactFiberScheduler(config);
|
||||
|
||||
function scheduleRootUpdate(
|
||||
current: Fiber,
|
||||
function updateContainerAtExpirationTime(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
currentTime: ExpirationTime,
|
||||
priorityLevel: PriorityLevel,
|
||||
expirationTime: ExpirationTime,
|
||||
callback: ?Function,
|
||||
) {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const current = container.current;
|
||||
|
||||
if (__DEV__) {
|
||||
if (ReactFiberInstrumentation.debugTool) {
|
||||
if (current.alternate === null) {
|
||||
ReactFiberInstrumentation.debugTool.onMountContainer(container);
|
||||
} else if (element === null) {
|
||||
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
|
||||
} else {
|
||||
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const context = getContextForSubtree(parentComponent);
|
||||
if (container.context === null) {
|
||||
container.context = context;
|
||||
} else {
|
||||
container.pendingContext = context;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (
|
||||
ReactDebugCurrentFiber.phase === 'render' &&
|
||||
@@ -347,6 +374,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
const update = {
|
||||
expirationTime,
|
||||
priorityLevel,
|
||||
partialState: {element},
|
||||
callback,
|
||||
isReplace: false,
|
||||
@@ -360,45 +388,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
function updateContainerAtExpirationTime(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
currentTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
callback: ?Function,
|
||||
) {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const current = container.current;
|
||||
|
||||
if (__DEV__) {
|
||||
if (ReactFiberInstrumentation.debugTool) {
|
||||
if (current.alternate === null) {
|
||||
ReactFiberInstrumentation.debugTool.onMountContainer(container);
|
||||
} else if (element === null) {
|
||||
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
|
||||
} else {
|
||||
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const context = getContextForSubtree(parentComponent);
|
||||
if (container.context === null) {
|
||||
container.context = context;
|
||||
} else {
|
||||
container.pendingContext = context;
|
||||
}
|
||||
|
||||
return scheduleRootUpdate(
|
||||
current,
|
||||
element,
|
||||
currentTime,
|
||||
expirationTime,
|
||||
callback,
|
||||
);
|
||||
}
|
||||
|
||||
function findHostInstance(fiber: Fiber): PI | null {
|
||||
const hostFiber = findCurrentHostFiber(fiber);
|
||||
if (hostFiber === null) {
|
||||
@@ -423,13 +412,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
callback: ?Function,
|
||||
): ExpirationTime {
|
||||
const current = container.current;
|
||||
const priorityLevel = computeUpdatePriorityForFiber(current);
|
||||
const currentTime = recalculateCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, current);
|
||||
const expirationTime = computeExpirationTimeForPriority(
|
||||
priorityLevel,
|
||||
currentTime,
|
||||
);
|
||||
return updateContainerAtExpirationTime(
|
||||
element,
|
||||
container,
|
||||
parentComponent,
|
||||
currentTime,
|
||||
priorityLevel,
|
||||
expirationTime,
|
||||
callback,
|
||||
);
|
||||
@@ -442,12 +436,16 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
expirationTime,
|
||||
callback,
|
||||
) {
|
||||
// TODO: Rethink this API. It's only used by the createBatch() API. Need
|
||||
// to revisit that implementation once suspenders are implemented.
|
||||
const priorityLevel = NoPriority;
|
||||
const currentTime = recalculateCurrentTime();
|
||||
return updateContainerAtExpirationTime(
|
||||
element,
|
||||
container,
|
||||
parentComponent,
|
||||
currentTime,
|
||||
priorityLevel,
|
||||
expirationTime,
|
||||
callback,
|
||||
);
|
||||
|
||||
+78
-39
@@ -12,6 +12,7 @@ import type {Fiber} from './ReactFiber';
|
||||
import type {FiberRoot, Batch} from './ReactFiberRoot';
|
||||
import type {HydrationContext} from './ReactFiberHydrationContext';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
@@ -80,6 +81,13 @@ import {
|
||||
startCommitLifeCyclesTimer,
|
||||
stopCommitLifeCyclesTimer,
|
||||
} from './ReactDebugFiberPerf';
|
||||
import {
|
||||
NoPriority,
|
||||
RenderPriority,
|
||||
SyncPriority,
|
||||
DeferredPriority,
|
||||
InteractivePriority,
|
||||
} from './ReactPriorityLevel';
|
||||
import {reset} from './ReactFiberStack';
|
||||
import {createWorkInProgress} from './ReactFiber';
|
||||
import {onCommitRoot} from './ReactFiberDevToolsHook';
|
||||
@@ -180,8 +188,9 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
hostContext,
|
||||
hydrationContext,
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
computeUpdatePriorityForFiber,
|
||||
recalculateCurrentTime,
|
||||
computeExpirationTimeForPriority,
|
||||
);
|
||||
const {completeWork} = ReactFiberCompleteWork(
|
||||
config,
|
||||
@@ -208,9 +217,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config,
|
||||
onCommitPhaseError,
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
computeUpdatePriorityForFiber,
|
||||
markLegacyErrorBoundaryAsFailed,
|
||||
recalculateCurrentTime,
|
||||
computeExpirationTimeForPriority,
|
||||
);
|
||||
const {
|
||||
now,
|
||||
@@ -228,10 +238,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
// Used to ensure computeUniqueAsyncExpiration is monotonically increases.
|
||||
let lastUniqueAsyncExpiration: number = 0;
|
||||
|
||||
// Represents the expiration time that incoming updates should use. (If this
|
||||
// is NoWork, use the default strategy: async updates in async mode, sync
|
||||
// Represents the priority that incoming updates should use. (If this is
|
||||
// NoPriority, use the default strategy: async updates in async mode, sync
|
||||
// updates in sync mode.)
|
||||
let expirationContext: ExpirationTime = NoWork;
|
||||
let priorityContext: PriorityLevel = NoPriority;
|
||||
|
||||
let isWorking: boolean = false;
|
||||
|
||||
@@ -990,6 +1000,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
sourceFiber,
|
||||
boundaryFiber,
|
||||
value,
|
||||
priorityLevel,
|
||||
startTime,
|
||||
expirationTime,
|
||||
) {
|
||||
@@ -997,6 +1008,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
const capturedValue = createCapturedValue(value, sourceFiber);
|
||||
const update = {
|
||||
expirationTime,
|
||||
priorityLevel,
|
||||
partialState: null,
|
||||
callback: null,
|
||||
isReplace: false,
|
||||
@@ -1011,6 +1023,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
function dispatch(
|
||||
sourceFiber: Fiber,
|
||||
value: mixed,
|
||||
priorityLevel: PriorityLevel,
|
||||
startTime: ExpirationTime,
|
||||
expirationTime: ExpirationTime,
|
||||
) {
|
||||
@@ -1036,6 +1049,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
sourceFiber,
|
||||
fiber,
|
||||
value,
|
||||
priorityLevel,
|
||||
startTime,
|
||||
expirationTime,
|
||||
);
|
||||
@@ -1044,7 +1058,14 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
break;
|
||||
// TODO: Handle async boundaries
|
||||
case HostRoot:
|
||||
scheduleCapture(sourceFiber, fiber, value, startTime, expirationTime);
|
||||
scheduleCapture(
|
||||
sourceFiber,
|
||||
fiber,
|
||||
value,
|
||||
priorityLevel,
|
||||
startTime,
|
||||
expirationTime,
|
||||
);
|
||||
return;
|
||||
}
|
||||
fiber = fiber.return;
|
||||
@@ -1057,6 +1078,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
sourceFiber,
|
||||
sourceFiber,
|
||||
value,
|
||||
priorityLevel,
|
||||
startTime,
|
||||
expirationTime,
|
||||
);
|
||||
@@ -1065,7 +1087,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
function onCommitPhaseError(fiber: Fiber, error: mixed) {
|
||||
const startTime = recalculateCurrentTime();
|
||||
return dispatch(fiber, error, startTime, Sync);
|
||||
return dispatch(fiber, error, SyncPriority, startTime, Sync);
|
||||
}
|
||||
|
||||
function computeAsyncExpiration(currentTime: ExpirationTime) {
|
||||
@@ -1098,23 +1120,20 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
return lastUniqueAsyncExpiration;
|
||||
}
|
||||
|
||||
function computeExpirationForFiber(
|
||||
currentTime: ExpirationTime,
|
||||
fiber: Fiber,
|
||||
) {
|
||||
let expirationTime;
|
||||
if (expirationContext !== NoWork) {
|
||||
// An explicit expiration context was set;
|
||||
expirationTime = expirationContext;
|
||||
function computeUpdatePriorityForFiber(fiber: Fiber): PriorityLevel {
|
||||
let priorityLevel;
|
||||
if (priorityContext !== NoPriority) {
|
||||
// An explicit priority context was set;
|
||||
priorityLevel = priorityContext;
|
||||
} else if (isWorking) {
|
||||
if (isCommitting) {
|
||||
// Updates that occur during the commit phase should have sync priority
|
||||
// by default.
|
||||
expirationTime = Sync;
|
||||
priorityLevel = SyncPriority;
|
||||
} else {
|
||||
// Updates during the render phase should expire at the same time as
|
||||
// the work that is being rendered.
|
||||
expirationTime = nextRenderExpirationTime;
|
||||
priorityLevel = RenderPriority;
|
||||
}
|
||||
} else {
|
||||
// No explicit expiration context was set, and we're not currently
|
||||
@@ -1122,28 +1141,36 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
if (fiber.mode & AsyncMode) {
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
// This is an interactive update
|
||||
expirationTime = computeInteractiveExpiration(currentTime);
|
||||
priorityLevel = InteractivePriority;
|
||||
} else {
|
||||
// This is an async update
|
||||
expirationTime = computeAsyncExpiration(currentTime);
|
||||
priorityLevel = DeferredPriority;
|
||||
}
|
||||
} else {
|
||||
// This is a sync update
|
||||
expirationTime = Sync;
|
||||
priorityLevel = SyncPriority;
|
||||
}
|
||||
}
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
// This is an interactive update. Keep track of the lowest pending
|
||||
// interactive expiration time. This allows us to synchronously flush
|
||||
// all interactive updates when needed.
|
||||
if (
|
||||
lowestPendingInteractiveExpirationTime === NoWork ||
|
||||
expirationTime > lowestPendingInteractiveExpirationTime
|
||||
) {
|
||||
lowestPendingInteractiveExpirationTime = expirationTime;
|
||||
}
|
||||
return priorityLevel;
|
||||
}
|
||||
|
||||
function computeExpirationTimeForPriority(
|
||||
priorityLevel: PriorityLevel,
|
||||
startTime: ExpirationTime,
|
||||
) {
|
||||
switch (priorityLevel) {
|
||||
case NoPriority:
|
||||
return nextRenderExpirationTime;
|
||||
case SyncPriority:
|
||||
return Sync;
|
||||
case RenderPriority:
|
||||
return nextRenderExpirationTime;
|
||||
case InteractivePriority:
|
||||
return computeInteractiveExpiration(startTime);
|
||||
case DeferredPriority:
|
||||
default:
|
||||
return computeAsyncExpiration(startTime);
|
||||
}
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
function retryOnPromiseResolution(
|
||||
@@ -1193,6 +1220,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
}
|
||||
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
// This is an interactive update. Keep track of the lowest pending
|
||||
// interactive expiration time. This allows us to synchronously flush
|
||||
// all interactive updates when needed.
|
||||
if (
|
||||
lowestPendingInteractiveExpirationTime === NoWork ||
|
||||
expirationTime > lowestPendingInteractiveExpirationTime
|
||||
) {
|
||||
lowestPendingInteractiveExpirationTime = expirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
let node = fiber;
|
||||
while (node !== null) {
|
||||
// Walk the parent path to the root and update each node's
|
||||
@@ -1262,13 +1301,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
|
||||
function deferredUpdates<A>(fn: () => A): A {
|
||||
const previousExpirationContext = expirationContext;
|
||||
const currentTime = recalculateCurrentTime();
|
||||
expirationContext = computeAsyncExpiration(currentTime);
|
||||
const previousPriorityContext = priorityContext;
|
||||
priorityContext = DeferredPriority;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
expirationContext = previousExpirationContext;
|
||||
priorityContext = previousPriorityContext;
|
||||
}
|
||||
}
|
||||
function syncUpdates<A, B, C0, D, R>(
|
||||
@@ -1278,12 +1316,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
c: C0,
|
||||
d: D,
|
||||
): R {
|
||||
const previousExpirationContext = expirationContext;
|
||||
expirationContext = Sync;
|
||||
const previousPriorityContext = priorityContext;
|
||||
priorityContext = SyncPriority;
|
||||
try {
|
||||
return fn(a, b, c, d);
|
||||
} finally {
|
||||
expirationContext = previousExpirationContext;
|
||||
priorityContext = previousPriorityContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1824,7 +1862,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
return {
|
||||
recalculateCurrentTime,
|
||||
computeExpirationForFiber,
|
||||
computeUpdatePriorityForFiber,
|
||||
computeExpirationTimeForPriority,
|
||||
scheduleWork,
|
||||
requestWork,
|
||||
flushRoot,
|
||||
|
||||
+10
-3
@@ -29,6 +29,7 @@ import {
|
||||
ShouldCapture,
|
||||
} from 'shared/ReactTypeOfSideEffect';
|
||||
import {Sync} from './ReactFiberExpirationTime';
|
||||
import {NoPriority} from './ReactPriorityLevel';
|
||||
|
||||
import {enableGetDerivedStateFromCatch} from 'shared/ReactFeatureFlags';
|
||||
|
||||
@@ -85,9 +86,10 @@ export default function(
|
||||
renderStartTime,
|
||||
renderExpirationTime,
|
||||
) {
|
||||
const slightlyHigherPriority = renderExpirationTime - 1;
|
||||
const slightlyEarlierExpirationTime = renderExpirationTime - 1;
|
||||
const loadingUpdate = {
|
||||
expirationTime: slightlyHigherPriority,
|
||||
expirationTime: slightlyEarlierExpirationTime,
|
||||
priorityLevel: NoPriority,
|
||||
partialState: true,
|
||||
callback: null,
|
||||
isReplace: true,
|
||||
@@ -99,6 +101,7 @@ export default function(
|
||||
|
||||
const revertUpdate = {
|
||||
expirationTime: renderExpirationTime,
|
||||
priorityLevel: NoPriority,
|
||||
partialState: false,
|
||||
callback: null,
|
||||
isReplace: true,
|
||||
@@ -107,7 +110,11 @@ export default function(
|
||||
next: null,
|
||||
};
|
||||
insertUpdateIntoFiber(workInProgress, revertUpdate);
|
||||
scheduleWork(workInProgress, renderStartTime, slightlyHigherPriority);
|
||||
scheduleWork(
|
||||
workInProgress,
|
||||
renderStartTime,
|
||||
slightlyEarlierExpirationTime,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {PriorityLevel} from './ReactPriorityLevel';
|
||||
import type {CapturedValue} from './ReactCapturedValue';
|
||||
|
||||
import {
|
||||
@@ -26,6 +27,7 @@ import warning from 'fbjs/lib/warning';
|
||||
import {StrictMode} from './ReactTypeOfMode';
|
||||
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {NoPriority} from './ReactPriorityLevel';
|
||||
|
||||
let didWarnUpdateInsideUpdate;
|
||||
|
||||
@@ -42,6 +44,7 @@ type Callback = mixed;
|
||||
|
||||
export type Update<State> = {
|
||||
expirationTime: ExpirationTime,
|
||||
priorityLevel: PriorityLevel,
|
||||
partialState: PartialState<any, any>,
|
||||
callback: Callback | null,
|
||||
isReplace: boolean,
|
||||
@@ -101,6 +104,27 @@ export function insertUpdateIntoQueue<State>(
|
||||
queue: UpdateQueue<State>,
|
||||
update: Update<State>,
|
||||
): void {
|
||||
const priorityLevel = update.priorityLevel;
|
||||
if (
|
||||
priorityLevel !== NoPriority &&
|
||||
queue.expirationTime <= update.expirationTime
|
||||
) {
|
||||
let node = queue.first;
|
||||
let latestExpirationTimeWithMatchingPriority = NoWork;
|
||||
while (node !== null) {
|
||||
if (
|
||||
node.priorityLevel === priorityLevel &&
|
||||
node.expirationTime > latestExpirationTimeWithMatchingPriority
|
||||
) {
|
||||
latestExpirationTimeWithMatchingPriority = node.expirationTime;
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
if (latestExpirationTimeWithMatchingPriority !== NoWork) {
|
||||
update.expirationTime = latestExpirationTimeWithMatchingPriority;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the update to the end of the list.
|
||||
if (queue.last === null) {
|
||||
// Queue is empty
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4;
|
||||
|
||||
export const NoPriority = 0;
|
||||
export const RenderPriority = 1;
|
||||
export const SyncPriority = 2;
|
||||
export const InteractivePriority = 3;
|
||||
export const DeferredPriority = 4;
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let Fragment;
|
||||
let ReactNoop;
|
||||
let ReactFeatureFlags;
|
||||
|
||||
describe('ReactExpiration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
Fragment = React.Fragment;
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
});
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('increases priority of updates as time progresses', () => {
|
||||
ReactNoop.render(<span prop="done" />);
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Nothing has expired yet because time hasn't advanced.
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Advance time a bit, but not enough to expire the low pri update.
|
||||
ReactNoop.expire(4500);
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Advance by another second. Now the update should expire and flush.
|
||||
ReactNoop.expire(1000);
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('done')]);
|
||||
});
|
||||
|
||||
it('coalesces updates to the same component', () => {
|
||||
class Foo extends React.Component {
|
||||
state = {step: 0};
|
||||
componentDidUpdate() {
|
||||
ReactNoop.yield(`Did update ${this.props.label}: ${this.state.step}`);
|
||||
}
|
||||
render() {
|
||||
ReactNoop.yield(`Render ${this.props.label}: ${this.state.step}`);
|
||||
return <span prop={`${this.props.label}: ${this.state.step}`} />;
|
||||
}
|
||||
}
|
||||
|
||||
let a = React.createRef();
|
||||
let b = React.createRef();
|
||||
ReactNoop.render(
|
||||
<Fragment>
|
||||
<Foo ref={a} label="A" />
|
||||
<Foo ref={b} label="B" />
|
||||
</Fragment>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
|
||||
a.value.setState({step: 1});
|
||||
|
||||
// Advance time to move into a new expiration bucket
|
||||
ReactNoop.expire(2000);
|
||||
|
||||
// Update A again. This update should coalesce with the previous update.
|
||||
a.value.setState({step: 2});
|
||||
// Update B. This is the first update, so it has nothing to coalesce with.
|
||||
b.value.setState({step: 2});
|
||||
|
||||
// Advance time by enough to expire step 1, but not step 2.
|
||||
ReactNoop.expire(4000);
|
||||
expect(ReactNoop.flushExpired()).toEqual([
|
||||
// Even though we called setState on A twice, both updates should flush in
|
||||
// a single batch.
|
||||
'Render A: 2',
|
||||
'Did update A: 2',
|
||||
// Update B has not expired yet, even though its setState was scheduled
|
||||
// at the same time as A
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('A: 2'), span('B: 0')]);
|
||||
|
||||
// Now expire B, too.
|
||||
ReactNoop.expire(2000);
|
||||
expect(ReactNoop.flushExpired()).toEqual([
|
||||
'Render B: 2',
|
||||
'Did update B: 2',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('A: 2'), span('B: 2')]);
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactNoop;
|
||||
|
||||
describe('ReactExpiration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
});
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('increases priority of updates as time progresses', () => {
|
||||
ReactNoop.render(<span prop="done" />);
|
||||
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Nothing has expired yet because time hasn't advanced.
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Advance time a bit, but not enough to expire the low pri update.
|
||||
ReactNoop.expire(4500);
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
// Advance by another second. Now the update should expire and flush.
|
||||
ReactNoop.expire(1000);
|
||||
ReactNoop.flushExpired();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('done')]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user