diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
index 65e4188dc9c44..c087ec93765c8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js
@@ -1306,44 +1306,25 @@ describe('ReactDOMServerHooks', () => {
// State update should trigger the ID to update, which changes the props
// of ChildWithID. This should cause ChildWithID to hydrate before Children
- gate(flags => {
- if (__DEV__) {
- expect(Scheduler).toFlushAndYieldThrough([
- 'Child with ID',
- // Fallbacks are immdiately committed in TestUtils version
- // of act
- // 'Child with ID',
- // 'Child with ID',
- 'Child One',
- 'Child Two',
- ]);
- } else if (flags.new) {
- // Upgrading a dehyrdating boundary works a little differently in
- // the new reconciler. After the update on the boundary is
- // scheduled, it waits until the end of the current time slice
- // before restarting at the higher priority.
- expect(Scheduler).toFlushAndYieldThrough([
- 'Child with ID',
- 'Child with ID',
- 'Child with ID',
- 'Child with ID',
- 'Child One',
- 'Child Two',
- ]);
- } else {
- // Whereas the old reconciler relies on a Scheduler hack to
- // interrupt the current task. It's not clear if this is any
- // better or worse, though. Regardless it's not a big deal since
- // the time slices aren't that big.
- expect(Scheduler).toFlushAndYieldThrough([
- 'Child with ID',
- 'Child with ID',
- 'Child with ID',
- 'Child One',
- 'Child Two',
- ]);
- }
- });
+ expect(Scheduler).toFlushAndYieldThrough(
+ __DEV__
+ ? [
+ 'Child with ID',
+ // Fallbacks are immediately committed in TestUtils version
+ // of act
+ // 'Child with ID',
+ // 'Child with ID',
+ 'Child One',
+ 'Child Two',
+ ]
+ : [
+ 'Child with ID',
+ 'Child with ID',
+ 'Child with ID',
+ 'Child One',
+ 'Child Two',
+ ],
+ );
expect(child1Ref.current).toBe(null);
expect(childWithIDRef.current).toEqual(
@@ -1691,15 +1672,24 @@ describe('ReactDOMServerHooks', () => {
ReactDOM.createRoot(container, {hydrate: true}).render();
- expect(() =>
- expect(() => Scheduler.unstable_flushAll()).toThrow(
+ if (gate(flags => flags.new)) {
+ expect(() => Scheduler.unstable_flushAll()).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
- ),
- ).toErrorDev([
- 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
- 'Do not read the value directly.',
- ]);
+ ]);
+ } else {
+ // In the old reconciler, the error isn't surfaced to the user. That
+ // part isn't important, as long as It warns.
+ expect(() =>
+ expect(() => Scheduler.unstable_flushAll()).toThrow(
+ 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
+ 'Do not read the value directly.',
+ ),
+ ).toErrorDev([
+ 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
+ 'Do not read the value directly.',
+ ]);
+ }
});
it('useOpaqueIdentifier throws if you try to add the result as a number in a child component wrapped in a Suspense', async () => {
@@ -1724,15 +1714,24 @@ describe('ReactDOMServerHooks', () => {
ReactDOM.createRoot(container, {hydrate: true}).render();
- expect(() =>
- expect(() => Scheduler.unstable_flushAll()).toThrow(
+ if (gate(flags => flags.new)) {
+ expect(() => Scheduler.unstable_flushAll()).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
- ),
- ).toErrorDev([
- 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
- 'Do not read the value directly.',
- ]);
+ ]);
+ } else {
+ // In the old reconciler, the error isn't surfaced to the user. That
+ // part isn't important, as long as It warns.
+ expect(() =>
+ expect(() => Scheduler.unstable_flushAll()).toThrow(
+ 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
+ 'Do not read the value directly.',
+ ),
+ ).toErrorDev([
+ 'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
+ 'Do not read the value directly.',
+ ]);
+ }
});
it('useOpaqueIdentifier with two opaque identifiers on the same page', () => {
diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js
index e993e4d571e8e..5b0e858d6af96 100644
--- a/packages/react-reconciler/src/ReactChildFiber.new.js
+++ b/packages/react-reconciler/src/ReactChildFiber.new.js
@@ -12,7 +12,7 @@ import type {ReactPortal} from 'shared/ReactTypes';
import type {BlockComponent} from 'react/src/ReactBlock';
import type {LazyComponent} from 'react/src/ReactLazy';
import type {Fiber} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import getComponentName from 'shared/getComponentName';
import {Placement, Deletion} from './ReactSideEffectTags';
@@ -377,15 +377,11 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
current: Fiber | null,
textContent: string,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
) {
if (current === null || current.tag !== HostText) {
// Insert
- const created = createFiberFromText(
- textContent,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
@@ -400,7 +396,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
if (current !== null) {
if (
@@ -442,11 +438,7 @@ function ChildReconciler(shouldTrackSideEffects) {
}
}
// Insert
- const created = createFiberFromElement(
- element,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, current, element);
created.return = returnFiber;
return created;
@@ -456,7 +448,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
current: Fiber | null,
portal: ReactPortal,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
if (
current === null ||
@@ -465,11 +457,7 @@ function ChildReconciler(shouldTrackSideEffects) {
current.stateNode.implementation !== portal.implementation
) {
// Insert
- const created = createFiberFromPortal(
- portal,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
@@ -484,7 +472,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
current: Fiber | null,
fragment: Iterable<*>,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
): Fiber {
if (current === null || current.tag !== Fragment) {
@@ -492,7 +480,7 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
- expirationTime,
+ lanes,
key,
);
created.return = returnFiber;
@@ -508,7 +496,7 @@ function ChildReconciler(shouldTrackSideEffects) {
function createChild(
returnFiber: Fiber,
newChild: any,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
@@ -517,7 +505,7 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
- expirationTime,
+ lanes,
);
created.return = returnFiber;
return created;
@@ -529,7 +517,7 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
- expirationTime,
+ lanes,
);
created.ref = coerceRef(returnFiber, null, newChild);
created.return = returnFiber;
@@ -539,7 +527,7 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
- expirationTime,
+ lanes,
);
created.return = returnFiber;
return created;
@@ -550,7 +538,7 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
- expirationTime,
+ lanes,
null,
);
created.return = returnFiber;
@@ -573,7 +561,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
// Update the fiber if the keys match, otherwise return null.
@@ -586,12 +574,7 @@ function ChildReconciler(shouldTrackSideEffects) {
if (key !== null) {
return null;
}
- return updateTextNode(
- returnFiber,
- oldFiber,
- '' + newChild,
- expirationTime,
- );
+ return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
}
if (typeof newChild === 'object' && newChild !== null) {
@@ -603,28 +586,18 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
oldFiber,
newChild.props.children,
- expirationTime,
+ lanes,
key,
);
}
- return updateElement(
- returnFiber,
- oldFiber,
- newChild,
- expirationTime,
- );
+ return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
}
}
case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
- return updatePortal(
- returnFiber,
- oldFiber,
- newChild,
- expirationTime,
- );
+ return updatePortal(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
}
@@ -636,13 +609,7 @@ function ChildReconciler(shouldTrackSideEffects) {
return null;
}
- return updateFragment(
- returnFiber,
- oldFiber,
- newChild,
- expirationTime,
- null,
- );
+ return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
}
throwOnInvalidObjectType(returnFiber, newChild);
@@ -662,18 +629,13 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
newIdx: number,
newChild: any,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
- return updateTextNode(
- returnFiber,
- matchedFiber,
- '' + newChild,
- expirationTime,
- );
+ return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes);
}
if (typeof newChild === 'object' && newChild !== null) {
@@ -688,40 +650,24 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
matchedFiber,
newChild.props.children,
- expirationTime,
+ lanes,
newChild.key,
);
}
- return updateElement(
- returnFiber,
- matchedFiber,
- newChild,
- expirationTime,
- );
+ return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
- return updatePortal(
- returnFiber,
- matchedFiber,
- newChild,
- expirationTime,
- );
+ return updatePortal(returnFiber, matchedFiber, newChild, lanes);
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
- return updateFragment(
- returnFiber,
- matchedFiber,
- newChild,
- expirationTime,
- null,
- );
+ return updateFragment(returnFiber, matchedFiber, newChild, lanes, null);
}
throwOnInvalidObjectType(returnFiber, newChild);
@@ -785,7 +731,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
// This algorithm can't optimize by searching from both ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
@@ -833,7 +779,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
oldFiber,
newChildren[newIdx],
- expirationTime,
+ lanes,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
@@ -877,11 +823,7 @@ function ChildReconciler(shouldTrackSideEffects) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
- const newFiber = createChild(
- returnFiber,
- newChildren[newIdx],
- expirationTime,
- );
+ const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
@@ -907,7 +849,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
newIdx,
newChildren[newIdx],
- expirationTime,
+ lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
@@ -944,7 +886,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildrenIterable: Iterable<*>,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
@@ -1023,12 +965,7 @@ function ChildReconciler(shouldTrackSideEffects) {
} else {
nextOldFiber = oldFiber.sibling;
}
- const newFiber = updateSlot(
- returnFiber,
- oldFiber,
- step.value,
- expirationTime,
- );
+ const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
@@ -1071,7 +1008,7 @@ function ChildReconciler(shouldTrackSideEffects) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; !step.done; newIdx++, step = newChildren.next()) {
- const newFiber = createChild(returnFiber, step.value, expirationTime);
+ const newFiber = createChild(returnFiber, step.value, lanes);
if (newFiber === null) {
continue;
}
@@ -1097,7 +1034,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
newIdx,
step.value,
- expirationTime,
+ lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
@@ -1134,7 +1071,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
@@ -1149,11 +1086,7 @@ function ChildReconciler(shouldTrackSideEffects) {
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
deleteRemainingChildren(returnFiber, currentFirstChild);
- const created = createFiberFromText(
- textContent,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
@@ -1162,7 +1095,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
@@ -1245,17 +1178,13 @@ function ChildReconciler(shouldTrackSideEffects) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
- expirationTime,
+ lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
- const created = createFiberFromElement(
- element,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
@@ -1266,7 +1195,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
const key = portal.key;
let child = currentFirstChild;
@@ -1293,11 +1222,7 @@ function ChildReconciler(shouldTrackSideEffects) {
child = child.sibling;
}
- const created = createFiberFromPortal(
- portal,
- returnFiber.mode,
- expirationTime,
- );
+ const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
@@ -1309,7 +1234,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber | null {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
@@ -1339,7 +1264,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- expirationTime,
+ lanes,
),
);
case REACT_PORTAL_TYPE:
@@ -1348,7 +1273,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- expirationTime,
+ lanes,
),
);
}
@@ -1360,7 +1285,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
'' + newChild,
- expirationTime,
+ lanes,
),
);
}
@@ -1370,7 +1295,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- expirationTime,
+ lanes,
);
}
@@ -1379,7 +1304,7 @@ function ChildReconciler(shouldTrackSideEffects) {
returnFiber,
currentFirstChild,
newChild,
- expirationTime,
+ lanes,
);
}
@@ -1462,13 +1387,10 @@ export function cloneChildFibers(
}
// Reset a workInProgress child set to prepare it for a second pass.
-export function resetChildFibers(
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
-): void {
+export function resetChildFibers(workInProgress: Fiber, lanes: Lanes): void {
let child = workInProgress.child;
while (child !== null) {
- resetWorkInProgress(child, renderExpirationTime);
+ resetWorkInProgress(child, lanes);
child = child.sibling;
}
}
diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js
index 741083a8650cc..b428e11374f43 100644
--- a/packages/react-reconciler/src/ReactFiber.new.js
+++ b/packages/react-reconciler/src/ReactFiber.new.js
@@ -18,7 +18,7 @@ import type {Fiber} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import type {OffscreenProps} from './ReactFiberOffscreenComponent';
@@ -66,7 +66,7 @@ import {
resolveFunctionForHotReloading,
resolveForwardRefForHotReloading,
} from './ReactFiberHotReloading.new';
-import {NoWork} from './ReactFiberExpirationTime.new';
+import {NoLanes} from './ReactFiberLane';
import {
NoMode,
ConcurrentMode,
@@ -150,8 +150,8 @@ function FiberNode(
this.firstEffect = null;
this.lastEffect = null;
- this.expirationTime_opaque = NoWork;
- this.childExpirationTime_opaque = NoWork;
+ this.lanes = NoLanes;
+ this.childLanes = NoLanes;
this.alternate = null;
@@ -314,9 +314,8 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
}
}
- workInProgress.childExpirationTime_opaque =
- current.childExpirationTime_opaque;
- workInProgress.expirationTime_opaque = current.expirationTime_opaque;
+ workInProgress.childLanes = current.childLanes;
+ workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
@@ -330,7 +329,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
currentDependencies === null
? null
: {
- expirationTime: currentDependencies.expirationTime,
+ lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
@@ -368,10 +367,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
}
// Used to reuse a Fiber for a second pass.
-export function resetWorkInProgress(
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
-) {
+export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
// This resets the Fiber to what createFiber or createWorkInProgress would
// have set the values to before during the first pass. Ideally this wouldn't
// be necessary but unfortunately many code paths reads from the workInProgress
@@ -392,8 +388,8 @@ export function resetWorkInProgress(
const current = workInProgress.alternate;
if (current === null) {
// Reset to createFiber's initial values.
- workInProgress.childExpirationTime_opaque = NoWork;
- workInProgress.expirationTime_opaque = renderExpirationTime;
+ workInProgress.childLanes = NoLanes;
+ workInProgress.lanes = renderLanes;
workInProgress.child = null;
workInProgress.memoizedProps = null;
@@ -412,9 +408,8 @@ export function resetWorkInProgress(
}
} else {
// Reset to the cloned values that createWorkInProgress would've.
- workInProgress.childExpirationTime_opaque =
- current.childExpirationTime_opaque;
- workInProgress.expirationTime_opaque = current.expirationTime_opaque;
+ workInProgress.childLanes = current.childLanes;
+ workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
@@ -428,7 +423,7 @@ export function resetWorkInProgress(
currentDependencies === null
? null
: {
- expirationTime: currentDependencies.expirationTime,
+ lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
@@ -470,7 +465,7 @@ export function createFiberFromTypeAndProps(
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
@@ -491,12 +486,7 @@ export function createFiberFromTypeAndProps(
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
- return createFiberFromFragment(
- pendingProps.children,
- mode,
- expirationTime,
- key,
- );
+ return createFiberFromFragment(pendingProps.children, mode, lanes, key);
case REACT_DEBUG_TRACING_MODE_TYPE:
fiberTag = Mode;
mode |= DebugTracingMode;
@@ -506,30 +496,16 @@ export function createFiberFromTypeAndProps(
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
- return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
+ return createFiberFromProfiler(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_TYPE:
- return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
+ return createFiberFromSuspense(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_LIST_TYPE:
- return createFiberFromSuspenseList(
- pendingProps,
- mode,
- expirationTime,
- key,
- );
+ return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
case REACT_OFFSCREEN_TYPE:
- return createFiberFromOffscreen(
- pendingProps,
- mode,
- expirationTime,
- key,
- );
+ return createFiberFromOffscreen(pendingProps, mode, lanes, key);
case REACT_LEGACY_HIDDEN_TYPE:
- return createFiberFromLegacyHidden(
- pendingProps,
- mode,
- expirationTime,
- key,
- );
+ return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
+
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
@@ -562,7 +538,7 @@ export function createFiberFromTypeAndProps(
type,
pendingProps,
mode,
- expirationTime,
+ lanes,
key,
);
}
@@ -573,7 +549,7 @@ export function createFiberFromTypeAndProps(
type,
pendingProps,
mode,
- expirationTime,
+ lanes,
key,
);
}
@@ -612,7 +588,7 @@ export function createFiberFromTypeAndProps(
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
@@ -620,7 +596,7 @@ export function createFiberFromTypeAndProps(
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
let owner = null;
if (__DEV__) {
@@ -635,7 +611,7 @@ export function createFiberFromElement(
pendingProps,
owner,
mode,
- expirationTime,
+ lanes,
);
if (__DEV__) {
fiber._debugSource = element._source;
@@ -647,11 +623,11 @@ export function createFiberFromElement(
export function createFiberFromFragment(
elements: ReactFragment,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(Fragment, elements, key, mode);
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
@@ -659,13 +635,13 @@ export function createFiberFromFundamental(
fundamentalComponent: ReactFundamentalComponent,
pendingProps: any,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
fiber.elementType = fundamentalComponent;
fiber.type = fundamentalComponent;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
@@ -673,20 +649,20 @@ function createFiberFromScope(
scope: ReactScope,
pendingProps: any,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
fiber.type = scope;
fiber.elementType = scope;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
function createFiberFromProfiler(
pendingProps: any,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
): Fiber {
if (__DEV__) {
@@ -699,7 +675,7 @@ function createFiberFromProfiler(
// TODO: The Profiler fiber shouldn't have a type. It has a tag.
fiber.elementType = REACT_PROFILER_TYPE;
fiber.type = REACT_PROFILER_TYPE;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
if (enableProfilerTimer) {
fiber.stateNode = {
@@ -714,7 +690,7 @@ function createFiberFromProfiler(
export function createFiberFromSuspense(
pendingProps: any,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
@@ -725,14 +701,14 @@ export function createFiberFromSuspense(
fiber.type = REACT_SUSPENSE_TYPE;
fiber.elementType = REACT_SUSPENSE_TYPE;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
export function createFiberFromSuspenseList(
pendingProps: any,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
@@ -743,14 +719,14 @@ export function createFiberFromSuspenseList(
fiber.type = REACT_SUSPENSE_LIST_TYPE;
}
fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
export function createFiberFromOffscreen(
pendingProps: OffscreenProps,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
@@ -761,14 +737,14 @@ export function createFiberFromOffscreen(
fiber.type = REACT_OFFSCREEN_TYPE;
}
fiber.elementType = REACT_OFFSCREEN_TYPE;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
export function createFiberFromLegacyHidden(
pendingProps: OffscreenProps,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
@@ -779,17 +755,17 @@ export function createFiberFromLegacyHidden(
fiber.type = REACT_LEGACY_HIDDEN_TYPE;
}
fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
export function createFiberFromText(
content: string,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
const fiber = createFiber(HostText, content, null, mode);
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
return fiber;
}
@@ -812,11 +788,11 @@ export function createFiberFromDehydratedFragment(
export function createFiberFromPortal(
portal: ReactPortal,
mode: TypeOfMode,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
): Fiber {
const pendingProps = portal.children !== null ? portal.children : [];
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
- fiber.expirationTime_opaque = expirationTime;
+ fiber.lanes = lanes;
fiber.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null, // Used by persistent updates
@@ -862,8 +838,8 @@ export function assignFiberPropertiesInDEV(
target.nextEffect = source.nextEffect;
target.firstEffect = source.firstEffect;
target.lastEffect = source.lastEffect;
- target.expirationTime_opaque = source.expirationTime_opaque;
- target.childExpirationTime_opaque = source.childExpirationTime_opaque;
+ target.lanes = source.lanes;
+ target.childLanes = source.childLanes;
target.alternate = source.alternate;
if (enableProfilerTimer) {
target.actualDuration = source.actualDuration;
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
index 576016d2f6945..012976aaf8a81 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
@@ -12,7 +12,7 @@ import type {BlockComponent} from 'react/src/ReactBlock';
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes, Lane} from './ReactFiberLane';
import type {
SuspenseState,
SuspenseListRenderState,
@@ -107,14 +107,17 @@ import {
initializeUpdateQueue,
} from './ReactUpdateQueue.new';
import {
- NoWork,
- Never,
- Sync,
- DefaultUpdateTime,
- isSameOrHigherPriority,
- isSameExpirationTime,
- bumpPriorityHigher,
-} from './ReactFiberExpirationTime.new';
+ NoLane,
+ NoLanes,
+ SyncLane,
+ OffscreenLane,
+ DefaultHydrationLane,
+ includesSomeLane,
+ laneToLanes,
+ removeLanes,
+ combineLanes,
+ getBumpedLaneForHydration,
+} from './ReactFiberLane';
import {
ConcurrentMode,
NoMode,
@@ -191,9 +194,9 @@ import {
retryDehydratedSuspenseBoundary,
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
- markUnprocessedUpdateTime,
+ markSkippedUpdateLanes,
getWorkInProgressRoot,
- pushRenderExpirationTime,
+ pushRenderLanes,
} from './ReactFiberWorkLoop.new';
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
@@ -228,7 +231,7 @@ export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
@@ -239,7 +242,7 @@ export function reconcileChildren(
workInProgress,
null,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
} else {
// If the current child is the same as the work in progress, it means that
@@ -252,7 +255,7 @@ export function reconcileChildren(
workInProgress,
current.child,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
}
}
@@ -261,7 +264,7 @@ function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
@@ -275,7 +278,7 @@ function forceUnmountCurrentAndReconcile(
workInProgress,
current.child,
null,
- renderExpirationTime,
+ renderLanes,
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
@@ -285,7 +288,7 @@ function forceUnmountCurrentAndReconcile(
workInProgress,
null,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
}
@@ -294,7 +297,7 @@ function updateForwardRef(
workInProgress: Fiber,
Component: any,
nextProps: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
@@ -321,7 +324,7 @@ function updateForwardRef(
// The rest is a fork of updateFunctionComponent
let nextChildren;
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
@@ -331,7 +334,7 @@ function updateForwardRef(
render,
nextProps,
ref,
- renderExpirationTime,
+ renderLanes,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
@@ -345,7 +348,7 @@ function updateForwardRef(
render,
nextProps,
ref,
- renderExpirationTime,
+ renderLanes,
);
} finally {
reenableLogs();
@@ -359,27 +362,18 @@ function updateForwardRef(
render,
nextProps,
ref,
- renderExpirationTime,
+ renderLanes,
);
}
if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderExpirationTime);
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ bailoutHooks(current, workInProgress, renderLanes);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -388,8 +382,8 @@ function updateMemoComponent(
workInProgress: Fiber,
Component: any,
nextProps: any,
- updateExpirationTime,
- renderExpirationTime: ExpirationTimeOpaque,
+ updateLanes: Lanes,
+ renderLanes: Lanes,
): null | Fiber {
if (current === null) {
const type = Component.type;
@@ -416,8 +410,8 @@ function updateMemoComponent(
workInProgress,
resolvedType,
nextProps,
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
);
}
if (__DEV__) {
@@ -439,7 +433,7 @@ function updateMemoComponent(
nextProps,
null,
workInProgress.mode,
- renderExpirationTime,
+ renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
@@ -461,7 +455,7 @@ function updateMemoComponent(
}
}
const currentChild = ((current.child: any): Fiber); // This is always exactly one child
- if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
+ if (!includesSomeLane(updateLanes, renderLanes)) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
@@ -469,11 +463,7 @@ function updateMemoComponent(
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// React DevTools reads this flag.
@@ -490,8 +480,8 @@ function updateSimpleMemoComponent(
workInProgress: Fiber,
Component: any,
nextProps: any,
- updateExpirationTime,
- renderExpirationTime: ExpirationTimeOpaque,
+ updateLanes: Lanes,
+ renderLanes: Lanes,
): null | Fiber {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens when the inner render suspends.
@@ -536,10 +526,10 @@ function updateSimpleMemoComponent(
(__DEV__ ? workInProgress.type === current.type : true)
) {
didReceiveUpdate = false;
- if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
- // The pending update priority was cleared at the beginning of
- // beginWork. We're about to bail out, but there might be additional
- // updates at a lower priority. Usually, the priority level of the
+ if (!includesSomeLane(renderLanes, updateLanes)) {
+ // The pending lanes were cleared at the beginning of beginWork. We're
+ // about to bail out, but there might be other lanes that weren't
+ // included in the current render. Usually, the priority level of the
// remaining updates is accumlated during the evaluation of the
// component (i.e. when processing the update queue). But since since
// we're bailing out early *without* evaluating the component, we need
@@ -550,11 +540,11 @@ function updateSimpleMemoComponent(
// contains hooks.
// TODO: Move the reset at in beginWork out of the common path so that
// this is no longer necessary.
- workInProgress.expirationTime_opaque = current.expirationTime_opaque;
+ workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
}
}
@@ -564,14 +554,14 @@ function updateSimpleMemoComponent(
workInProgress,
Component,
nextProps,
- renderExpirationTime,
+ renderLanes,
);
}
function updateOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
const nextProps: OffscreenProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
@@ -580,71 +570,57 @@ function updateOffscreenComponent(
current !== null ? current.memoizedState : null;
if (nextProps.mode === 'hidden') {
- if (
- !isSameExpirationTime(renderExpirationTime, (Never: ExpirationTimeOpaque))
- ) {
- let nextBaseTime;
+ if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
+ let nextBaseLanes;
if (prevState !== null) {
- const prevBaseTime = prevState.baseTime;
- nextBaseTime = !isSameOrHigherPriority(
- prevBaseTime,
- renderExpirationTime,
- )
- ? prevBaseTime
- : renderExpirationTime;
+ const prevBaseLanes = prevState.baseLanes;
+ nextBaseLanes = combineLanes(prevBaseLanes, renderLanes);
} else {
- nextBaseTime = renderExpirationTime;
+ nextBaseLanes = renderLanes;
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
if (enableSchedulerTracing) {
- markSpawnedWork((Never: ExpirationTimeOpaque));
+ markSpawnedWork((OffscreenLane: Lane));
}
- workInProgress.expirationTime_opaque = workInProgress.childExpirationTime_opaque = Never;
+ workInProgress.lanes = workInProgress.childLanes = laneToLanes(
+ OffscreenLane,
+ );
const nextState: OffscreenState = {
- baseTime: nextBaseTime,
+ baseLanes: nextBaseLanes,
};
workInProgress.memoizedState = nextState;
// We're about to bail out, but we need to push this to the stack anyway
// to avoid a push/pop misalignment.
- pushRenderExpirationTime(workInProgress, nextBaseTime);
+ pushRenderLanes(workInProgress, nextBaseLanes);
return null;
} else {
- // Rendering at offscreen, so we can clear the base time.
+ // Rendering at offscreen, so we can clear the base lanes.
const nextState: OffscreenState = {
- baseTime: NoWork,
+ baseLanes: NoLanes,
};
workInProgress.memoizedState = nextState;
- pushRenderExpirationTime(workInProgress, renderExpirationTime);
+ // Push the lanes that were skipped when we bailed out.
+ const subtreeRenderLanes =
+ prevState !== null ? prevState.baseLanes : renderLanes;
+ pushRenderLanes(workInProgress, subtreeRenderLanes);
}
} else {
- let subtreeRenderTime;
+ let subtreeRenderLanes;
if (prevState !== null) {
- const baseTime = prevState.baseTime;
- subtreeRenderTime = !isSameOrHigherPriority(
- baseTime,
- renderExpirationTime,
- )
- ? baseTime
- : renderExpirationTime;
-
+ subtreeRenderLanes = combineLanes(prevState.baseLanes, renderLanes);
// Since we're not hidden anymore, reset the state
workInProgress.memoizedState = null;
} else {
// We weren't previously hidden, and we still aren't, so there's nothing
// special to do. Need to push to the stack regardless, though, to avoid
// a push/pop misalignment.
- subtreeRenderTime = renderExpirationTime;
+ subtreeRenderLanes = renderLanes;
}
- pushRenderExpirationTime(workInProgress, subtreeRenderTime);
+ pushRenderLanes(workInProgress, subtreeRenderLanes);
}
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -656,37 +632,27 @@ const updateLegacyHiddenComponent = updateOffscreenComponent;
function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps.children;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
@@ -699,12 +665,7 @@ function updateProfiler(
}
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -724,7 +685,7 @@ function updateFunctionComponent(
workInProgress,
Component,
nextProps: any,
- renderExpirationTime,
+ renderLanes,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
@@ -749,7 +710,7 @@ function updateFunctionComponent(
}
let nextChildren;
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
@@ -759,7 +720,7 @@ function updateFunctionComponent(
Component,
nextProps,
context,
- renderExpirationTime,
+ renderLanes,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
@@ -773,7 +734,7 @@ function updateFunctionComponent(
Component,
nextProps,
context,
- renderExpirationTime,
+ renderLanes,
);
} finally {
reenableLogs();
@@ -787,27 +748,18 @@ function updateFunctionComponent(
Component,
nextProps,
context,
- renderExpirationTime,
+ renderLanes,
);
}
if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderExpirationTime);
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ bailoutHooks(current, workInProgress, renderLanes);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -816,7 +768,7 @@ function updateBlock(
workInProgress: Fiber,
block: BlockComponent,
nextProps: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
@@ -827,7 +779,7 @@ function updateBlock(
// The rest is a fork of updateFunctionComponent
let nextChildren;
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
@@ -837,7 +789,7 @@ function updateBlock(
render,
nextProps,
data,
- renderExpirationTime,
+ renderLanes,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
@@ -851,7 +803,7 @@ function updateBlock(
render,
nextProps,
data,
- renderExpirationTime,
+ renderLanes,
);
} finally {
reenableLogs();
@@ -865,27 +817,18 @@ function updateBlock(
render,
nextProps,
data,
- renderExpirationTime,
+ renderLanes,
);
}
if (current !== null && !didReceiveUpdate) {
- bailoutHooks(current, workInProgress, renderExpirationTime);
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ bailoutHooks(current, workInProgress, renderLanes);
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -893,8 +836,8 @@ function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
- nextProps,
- renderExpirationTime: ExpirationTimeOpaque,
+ nextProps: any,
+ renderLanes: Lanes,
) {
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
@@ -922,7 +865,7 @@ function updateClassComponent(
} else {
hasContext = false;
}
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
const instance = workInProgress.stateNode;
let shouldUpdate;
@@ -939,12 +882,7 @@ function updateClassComponent(
}
// In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, Component, nextProps);
- mountClassInstance(
- workInProgress,
- Component,
- nextProps,
- renderExpirationTime,
- );
+ mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
@@ -952,7 +890,7 @@ function updateClassComponent(
workInProgress,
Component,
nextProps,
- renderExpirationTime,
+ renderLanes,
);
} else {
shouldUpdate = updateClassInstance(
@@ -960,7 +898,7 @@ function updateClassComponent(
workInProgress,
Component,
nextProps,
- renderExpirationTime,
+ renderLanes,
);
}
const nextUnitOfWork = finishClassComponent(
@@ -969,7 +907,7 @@ function updateClassComponent(
Component,
shouldUpdate,
hasContext,
- renderExpirationTime,
+ renderLanes,
);
if (__DEV__) {
const inst = workInProgress.stateNode;
@@ -993,7 +931,7 @@ function finishClassComponent(
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
@@ -1006,11 +944,7 @@ function finishClassComponent(
invalidateContextProvider(workInProgress, Component, false);
}
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode;
@@ -1064,15 +998,10 @@ function finishClassComponent(
current,
workInProgress,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
} else {
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
// Memoize state using the values we just used to render.
@@ -1102,7 +1031,7 @@ function pushHostRootContext(workInProgress) {
pushHostContainer(workInProgress, root.containerInfo);
}
-function updateHostRoot(current, workInProgress, renderExpirationTime) {
+function updateHostRoot(current, workInProgress, renderLanes) {
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
invariant(
@@ -1115,20 +1044,14 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
cloneUpdateQueue(current, workInProgress);
- processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
+ processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (nextChildren === prevChildren) {
- // If the state is the same as before, that's a bailout because we had
- // no work that expires at this time.
resetHydrationState();
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const root: FiberRoot = workInProgress.stateNode;
if (root.hydrate && enterHydrationState(workInProgress)) {
@@ -1141,7 +1064,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
workInProgress,
null,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
workInProgress.child = child;
@@ -1159,12 +1082,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
resetHydrationState();
}
return workInProgress.child;
@@ -1173,7 +1091,7 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
pushHostContext(workInProgress);
@@ -1221,12 +1139,7 @@ function updateHostComponent(
nextChildren = wrappedChildren;
}
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -1243,8 +1156,8 @@ function mountLazyComponent(
_current,
workInProgress,
elementType,
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
) {
if (_current !== null) {
// A lazy component only mounts if it suspended inside a non-
@@ -1280,7 +1193,7 @@ function mountLazyComponent(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
return child;
}
@@ -1295,7 +1208,7 @@ function mountLazyComponent(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
return child;
}
@@ -1310,7 +1223,7 @@ function mountLazyComponent(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
return child;
}
@@ -1333,8 +1246,8 @@ function mountLazyComponent(
workInProgress,
Component,
resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
);
return child;
}
@@ -1346,7 +1259,7 @@ function mountLazyComponent(
workInProgress,
Component,
props,
- renderExpirationTime,
+ renderLanes,
);
return child;
}
@@ -1380,7 +1293,7 @@ function mountIncompleteClassComponent(
workInProgress,
Component,
nextProps,
- renderExpirationTime,
+ renderLanes,
) {
if (_current !== null) {
// An incomplete component only mounts if it suspended inside a non-
@@ -1408,15 +1321,10 @@ function mountIncompleteClassComponent(
} else {
hasContext = false;
}
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
constructClassInstance(workInProgress, Component, nextProps);
- mountClassInstance(
- workInProgress,
- Component,
- nextProps,
- renderExpirationTime,
- );
+ mountClassInstance(workInProgress, Component, nextProps, renderLanes);
return finishClassComponent(
null,
@@ -1424,7 +1332,7 @@ function mountIncompleteClassComponent(
Component,
true,
hasContext,
- renderExpirationTime,
+ renderLanes,
);
}
@@ -1432,7 +1340,7 @@ function mountIndeterminateComponent(
_current,
workInProgress,
Component,
- renderExpirationTime,
+ renderLanes,
) {
if (_current !== null) {
// An indeterminate component only mounts if it suspended inside a non-
@@ -1456,7 +1364,7 @@ function mountIndeterminateComponent(
context = getMaskedContext(workInProgress, unmaskedContext);
}
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
let value;
if (__DEV__) {
@@ -1489,7 +1397,7 @@ function mountIndeterminateComponent(
Component,
props,
context,
- renderExpirationTime,
+ renderLanes,
);
setIsRendering(false);
} else {
@@ -1499,7 +1407,7 @@ function mountIndeterminateComponent(
Component,
props,
context,
- renderExpirationTime,
+ renderLanes,
);
}
// React DevTools reads this flag.
@@ -1591,14 +1499,14 @@ function mountIndeterminateComponent(
}
adoptClassInstance(workInProgress, value);
- mountClassInstance(workInProgress, Component, props, renderExpirationTime);
+ mountClassInstance(workInProgress, Component, props, renderLanes);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
- renderExpirationTime,
+ renderLanes,
);
} else {
// Proceed under the assumption that this is a function component
@@ -1624,14 +1532,14 @@ function mountIndeterminateComponent(
Component,
props,
context,
- renderExpirationTime,
+ renderLanes,
);
} finally {
reenableLogs();
}
}
}
- reconcileChildren(null, workInProgress, value, renderExpirationTime);
+ reconcileChildren(null, workInProgress, value, renderLanes);
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
}
@@ -1719,30 +1627,21 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
const SUSPENDED_MARKER: SuspenseState = {
dehydrated: null,
- retryTime: NoWork,
+ retryLane: NoLane,
};
-function mountSuspenseOffscreenState(
- renderExpirationTime: ExpirationTimeOpaque,
-): OffscreenState {
+function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
return {
- baseTime: renderExpirationTime,
+ baseLanes: renderLanes,
};
}
function updateSuspenseOffscreenState(
prevOffscreenState: OffscreenState,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): OffscreenState {
- const prevBaseTime = prevOffscreenState.baseTime;
return {
- // Choose whichever time is inclusive of the other one. This represents
- // the union of all the levels that suspended.
- baseTime:
- !isSameExpirationTime(prevBaseTime, (NoWork: ExpirationTimeOpaque)) &&
- !isSameOrHigherPriority(prevBaseTime, renderExpirationTime)
- ? prevBaseTime
- : renderExpirationTime,
+ baseLanes: combineLanes(prevOffscreenState.baseLanes, renderLanes),
};
}
@@ -1750,7 +1649,7 @@ function shouldRemainOnFallback(
suspenseContext: SuspenseContext,
current: null | Fiber,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// If we're already showing a fallback, there are cases where we need to
// remain on that fallback regardless of whether the content has resolved.
@@ -1773,44 +1672,12 @@ function shouldRemainOnFallback(
);
}
-function getRemainingWorkInPrimaryTree(
- current: Fiber,
- workInProgress: Fiber,
- renderExpirationTime,
-) {
- const currentChildExpirationTime = current.childExpirationTime_opaque;
- if (
- !isSameOrHigherPriority(currentChildExpirationTime, renderExpirationTime)
- ) {
- // The highest priority remaining work is not part of this render. So the
- // remaining work has not changed.
- return currentChildExpirationTime;
- }
-
- if ((workInProgress.mode & BlockingMode) !== NoMode) {
- // The highest priority remaining work is part of this render. Since we only
- // keep track of the highest level, we don't know if there's a lower
- // priority level scheduled. As a compromise, we'll render at the lowest
- // known level in the entire tree, since that will include everything.
- // TODO: If expirationTime were a bitmask where each bit represents a
- // separate task thread, this would be: currentChildBits & ~renderBits
- const root = getWorkInProgressRoot();
- if (root !== null) {
- const lastPendingTime = root.lastPendingTime_opaque;
- if (!isSameOrHigherPriority(lastPendingTime, renderExpirationTime)) {
- return lastPendingTime;
- }
- }
- }
- // In legacy mode, there's no work left.
- return NoWork;
+function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes) {
+ // TODO: Should not remove render lanes that were pinged during this render
+ return removeLanes(current.childLanes, renderLanes);
}
-function updateSuspenseComponent(
- current,
- workInProgress,
- renderExpirationTime,
-) {
+function updateSuspenseComponent(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
// This is used by DevTools to force a boundary to suspend.
@@ -1831,7 +1698,7 @@ function updateSuspenseComponent(
suspenseContext,
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
)
) {
// Something in this boundary's subtree already suspended. Switch to
@@ -1903,7 +1770,7 @@ function updateSuspenseComponent(
return mountDehydratedSuspenseComponent(
workInProgress,
dehydrated,
- renderExpirationTime,
+ renderLanes,
);
}
}
@@ -1917,11 +1784,11 @@ function updateSuspenseComponent(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
- renderExpirationTime,
+ renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
- renderExpirationTime,
+ renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackFragment;
@@ -1930,7 +1797,7 @@ function updateSuspenseComponent(
return mountSuspensePrimaryChildren(
workInProgress,
nextPrimaryChildren,
- renderExpirationTime,
+ renderLanes,
);
}
} else {
@@ -1952,7 +1819,7 @@ function updateSuspenseComponent(
workInProgress,
dehydrated,
prevState,
- renderExpirationTime,
+ renderLanes,
);
} else if (
(workInProgress.memoizedState: null | SuspenseState) !== null
@@ -1974,11 +1841,11 @@ function updateSuspenseComponent(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
- renderExpirationTime,
+ renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(
- renderExpirationTime,
+ renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
@@ -1994,22 +1861,18 @@ function updateSuspenseComponent(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
- renderExpirationTime,
+ renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
const prevOffscreenState: OffscreenState | null = (current.child: any)
.memoizedState;
primaryChildFragment.memoizedState =
prevOffscreenState === null
- ? mountSuspenseOffscreenState(renderExpirationTime)
- : updateSuspenseOffscreenState(
- prevOffscreenState,
- renderExpirationTime,
- );
- primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
+ ? mountSuspenseOffscreenState(renderLanes)
+ : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
+ primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
- workInProgress,
- renderExpirationTime,
+ renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
@@ -2019,7 +1882,7 @@ function updateSuspenseComponent(
current,
workInProgress,
nextPrimaryChildren,
- renderExpirationTime,
+ renderLanes,
);
workInProgress.memoizedState = null;
return primaryChildFragment;
@@ -2035,22 +1898,18 @@ function updateSuspenseComponent(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
- renderExpirationTime,
+ renderLanes,
);
const primaryChildFragment: Fiber = (workInProgress.child: any);
const prevOffscreenState: OffscreenState | null = (current.child: any)
.memoizedState;
primaryChildFragment.memoizedState =
prevOffscreenState === null
- ? mountSuspenseOffscreenState(renderExpirationTime)
- : updateSuspenseOffscreenState(
- prevOffscreenState,
- renderExpirationTime,
- );
- primaryChildFragment.childExpirationTime_opaque = getRemainingWorkInPrimaryTree(
+ ? mountSuspenseOffscreenState(renderLanes)
+ : updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
+ primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
- workInProgress,
- renderExpirationTime,
+ renderLanes,
);
// Skip the primary children, and continue working on the
// fallback children.
@@ -2064,7 +1923,7 @@ function updateSuspenseComponent(
current,
workInProgress,
nextPrimaryChildren,
- renderExpirationTime,
+ renderLanes,
);
workInProgress.memoizedState = null;
return primaryChildFragment;
@@ -2076,7 +1935,7 @@ function updateSuspenseComponent(
function mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
- renderExpirationTime,
+ renderLanes,
) {
const mode = workInProgress.mode;
const primaryChildProps: OffscreenProps = {
@@ -2086,7 +1945,7 @@ function mountSuspensePrimaryChildren(
const primaryChildFragment = createFiberFromOffscreen(
primaryChildProps,
mode,
- renderExpirationTime,
+ renderLanes,
null,
);
primaryChildFragment.return = workInProgress;
@@ -2098,7 +1957,7 @@ function mountSuspenseFallbackChildren(
workInProgress,
primaryChildren,
fallbackChildren,
- renderExpirationTime,
+ renderLanes,
) {
const mode = workInProgress.mode;
const progressedPrimaryFragment: Fiber | null = workInProgress.child;
@@ -2114,7 +1973,7 @@ function mountSuspenseFallbackChildren(
// In legacy mode, we commit the primary tree as if it successfully
// completed, even though it's in an inconsistent state.
primaryChildFragment = progressedPrimaryFragment;
- primaryChildFragment.childExpirationTime_opaque = NoWork;
+ primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -2131,20 +1990,20 @@ function mountSuspenseFallbackChildren(
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
- renderExpirationTime,
+ renderLanes,
null,
);
} else {
primaryChildFragment = createFiberFromOffscreen(
primaryChildProps,
mode,
- NoWork,
+ NoLanes,
null,
);
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
- renderExpirationTime,
+ renderLanes,
null,
);
}
@@ -2169,7 +2028,7 @@ function updateSuspensePrimaryChildren(
current,
workInProgress,
primaryChildren,
- renderExpirationTime,
+ renderLanes,
) {
const currentPrimaryChildFragment: Fiber = (current.child: any);
const currentFallbackChildFragment: Fiber | null =
@@ -2183,7 +2042,7 @@ function updateSuspensePrimaryChildren(
},
);
if ((workInProgress.mode & BlockingMode) === NoMode) {
- primaryChildFragment.expirationTime_opaque = renderExpirationTime;
+ primaryChildFragment.lanes = renderLanes;
}
primaryChildFragment.return = workInProgress;
primaryChildFragment.sibling = null;
@@ -2203,7 +2062,7 @@ function updateSuspenseFallbackChildren(
workInProgress,
primaryChildren,
fallbackChildren,
- renderExpirationTime,
+ renderLanes,
) {
const mode = workInProgress.mode;
const currentPrimaryChildFragment: Fiber = (current.child: any);
@@ -2221,7 +2080,7 @@ function updateSuspenseFallbackChildren(
// completed, even though it's in an inconsistent state.
const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
primaryChildFragment = progressedPrimaryFragment;
- primaryChildFragment.childExpirationTime_opaque = NoWork;
+ primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -2268,7 +2127,7 @@ function updateSuspenseFallbackChildren(
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
- renderExpirationTime,
+ renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense boundary) already
@@ -2287,15 +2146,10 @@ function updateSuspenseFallbackChildren(
function retrySuspenseComponentWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
// This will add the old fiber to the deletion list
- reconcileChildFibers(
- workInProgress,
- current.child,
- null,
- renderExpirationTime,
- );
+ reconcileChildFibers(workInProgress, current.child, null, renderLanes);
// We're now not suspended nor dehydrated.
const nextProps = workInProgress.pendingProps;
@@ -2303,7 +2157,7 @@ function retrySuspenseComponentWithoutHydrating(
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
- renderExpirationTime,
+ renderLanes,
);
// Needs a placement effect because the parent (the Suspense boundary) already
// mounted but this is a new fiber.
@@ -2318,19 +2172,19 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
workInProgress,
primaryChildren,
fallbackChildren,
- renderExpirationTime,
+ renderLanes,
) {
const mode = workInProgress.mode;
const primaryChildFragment = createFiberFromOffscreen(
primaryChildren,
mode,
- NoWork,
+ NoLanes,
null,
);
const fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
- renderExpirationTime,
+ renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense
@@ -2345,12 +2199,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
if ((workInProgress.mode & BlockingMode) !== NoMode) {
// We will have dropped the effect list which contains the
// deletion. We need to reconcile to delete the current child.
- reconcileChildFibers(
- workInProgress,
- current.child,
- null,
- renderExpirationTime,
- );
+ reconcileChildFibers(workInProgress, current.child, null, renderLanes);
}
return fallbackChildFragment;
@@ -2359,7 +2208,7 @@ function mountSuspenseFallbackAfterRetryWithoutHydrating(
function mountDehydratedSuspenseComponent(
workInProgress: Fiber,
suspenseInstance: SuspenseInstance,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): null | Fiber {
// During the first pass, we'll bail out and not drill into the children.
// Instead, we'll leave the content in place and try to hydrate it later.
@@ -2373,7 +2222,7 @@ function mountDehydratedSuspenseComponent(
'the server rendered components.',
);
}
- workInProgress.expirationTime_opaque = Sync;
+ workInProgress.lanes = laneToLanes(SyncLane);
} else if (isSuspenseInstanceFallback(suspenseInstance)) {
// This is a client-only boundary. Since we won't get any content from the server
// for this, we need to schedule that at a higher priority based on when it would
@@ -2387,17 +2236,16 @@ function mountDehydratedSuspenseComponent(
// time. This will mean that Suspense timeouts are slightly shifted to later than
// they should be.
// Schedule a normal pri update to render this content.
- const newExpirationTime = DefaultUpdateTime;
if (enableSchedulerTracing) {
- markSpawnedWork(newExpirationTime);
+ markSpawnedWork(DefaultHydrationLane);
}
- workInProgress.expirationTime_opaque = newExpirationTime;
+ workInProgress.lanes = laneToLanes(DefaultHydrationLane);
} else {
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
- workInProgress.expirationTime_opaque = Never;
+ workInProgress.lanes = laneToLanes(OffscreenLane);
if (enableSchedulerTracing) {
- markSpawnedWork((Never: ExpirationTimeOpaque));
+ markSpawnedWork(OffscreenLane);
}
}
return null;
@@ -2408,7 +2256,7 @@ function updateDehydratedSuspenseComponent(
workInProgress: Fiber,
suspenseInstance: SuspenseInstance,
suspenseState: SuspenseState,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): null | Fiber {
// We should never be hydrating at this point because it is the first pass,
// but after we've already committed once.
@@ -2418,7 +2266,7 @@ function updateDehydratedSuspenseComponent(
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
}
@@ -2429,36 +2277,30 @@ function updateDehydratedSuspenseComponent(
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
}
- // We use childExpirationTime to indicate that a child might depend on context, so if
+ // We use lanes to indicate that a child might depend on context, so if
// any context has changed, we need to treat is as if the input might have changed.
- const hasContextChanged = isSameOrHigherPriority(
- current.childExpirationTime_opaque,
- renderExpirationTime,
- );
+ const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
if (didReceiveUpdate || hasContextChanged) {
// This boundary has changed since the first render. This means that we are now unable to
- // hydrate it. We might still be able to hydrate it using an earlier expiration time, if
- // we are rendering at lower expiration than sync.
- if (
- !isSameOrHigherPriority(
- renderExpirationTime,
- (Sync: ExpirationTimeOpaque),
- )
- ) {
+ // hydrate it. We might still be able to hydrate it using a higher priority lane.
+ const root = getWorkInProgressRoot();
+ if (root !== null) {
+ const attemptHydrationAtLane = getBumpedLaneForHydration(
+ root,
+ renderLanes,
+ );
if (
- isSameOrHigherPriority(renderExpirationTime, suspenseState.retryTime)
+ attemptHydrationAtLane !== NoLane &&
+ attemptHydrationAtLane !== suspenseState.retryLane
) {
- // This render is even higher pri than we've seen before, let's try again
- // at even higher pri.
- const attemptHydrationAtExpirationTime = bumpPriorityHigher(
- renderExpirationTime,
- );
- suspenseState.retryTime = attemptHydrationAtExpirationTime;
- scheduleUpdateOnFiber(current, attemptHydrationAtExpirationTime);
- // TODO: Early abort this render.
+ // Intentionally mutating since this render will get interrupted. This
+ // is one of the very rare times where we mutate the current tree
+ // during the render phase.
+ suspenseState.retryLane = attemptHydrationAtLane;
+ scheduleUpdateOnFiber(current, attemptHydrationAtLane);
} else {
// We have already tried to ping at a higher priority than we're rendering with
// so if we got here, we must have failed to hydrate at those levels. We must
@@ -2468,6 +2310,7 @@ function updateDehydratedSuspenseComponent(
// an opportunity to hydrate before this pass commits.
}
}
+
// If we have scheduled higher pri work above, this will probably just abort the render
// since we now have higher priority work, but in case it doesn't, we need to prepare to
// render something, if we time out. Even if that requires us to delete everything and
@@ -2477,7 +2320,7 @@ function updateDehydratedSuspenseComponent(
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
} else if (isSuspenseInstancePending(suspenseInstance)) {
// This component is still pending more data from the server, so we can't hydrate its
@@ -2509,7 +2352,7 @@ function updateDehydratedSuspenseComponent(
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
- renderExpirationTime,
+ renderLanes,
);
// Mark the children as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
@@ -2522,32 +2365,19 @@ function updateDehydratedSuspenseComponent(
}
}
-function scheduleWorkOnFiber(
- fiber: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
-) {
- if (
- !isSameOrHigherPriority(fiber.expirationTime_opaque, renderExpirationTime)
- ) {
- fiber.expirationTime_opaque = renderExpirationTime;
- }
+function scheduleWorkOnFiber(fiber: Fiber, renderLanes: Lanes) {
+ fiber.lanes = combineLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.expirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- alternate.expirationTime_opaque = renderExpirationTime;
+ if (alternate !== null) {
+ alternate.lanes = combineLanes(alternate.lanes, renderLanes);
}
- scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
+ scheduleWorkOnParentPath(fiber.return, renderLanes);
}
function propagateSuspenseContextChange(
workInProgress: Fiber,
firstChild: null | Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): void {
// Mark any Suspense boundaries with fallbacks as having work to do.
// If they were previously forced into fallbacks, they may now be able
@@ -2557,7 +2387,7 @@ function propagateSuspenseContextChange(
if (node.tag === SuspenseComponent) {
const state: SuspenseState | null = node.memoizedState;
if (state !== null) {
- scheduleWorkOnFiber(node, renderExpirationTime);
+ scheduleWorkOnFiber(node, renderLanes);
}
} else if (node.tag === SuspenseListComponent) {
// If the tail is hidden there might not be an Suspense boundaries
@@ -2565,7 +2395,7 @@ function propagateSuspenseContextChange(
// list itself.
// We don't have to traverse to the children of the list since
// the list will propagate the change when it rerenders.
- scheduleWorkOnFiber(node, renderExpirationTime);
+ scheduleWorkOnFiber(node, renderLanes);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
@@ -2797,7 +2627,7 @@ function initSuspenseListRenderState(
function updateSuspenseListComponent(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
const nextProps = workInProgress.pendingProps;
const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
@@ -2808,7 +2638,7 @@ function updateSuspenseListComponent(
validateTailOptions(tailMode, revealOrder);
validateSuspenseListChildren(newChildren, revealOrder);
- reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
@@ -2832,7 +2662,7 @@ function updateSuspenseListComponent(
propagateSuspenseContextChange(
workInProgress,
workInProgress.child,
- renderExpirationTime,
+ renderLanes,
);
}
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
@@ -2925,7 +2755,7 @@ function updateSuspenseListComponent(
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const nextChildren = workInProgress.pendingProps;
@@ -2939,15 +2769,10 @@ function updatePortalComponent(
workInProgress,
null,
nextChildren,
- renderExpirationTime,
+ renderLanes,
);
} else {
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
@@ -2955,7 +2780,7 @@ function updatePortalComponent(
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
const providerType: ReactProviderType = workInProgress.type;
const context: ReactContext = providerType._context;
@@ -2987,23 +2812,18 @@ function updateContextProvider(
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
- propagateContextChange(
- workInProgress,
- context,
- changedBits,
- renderExpirationTime,
- );
+ propagateContextChange(workInProgress, context, changedBits, renderLanes);
}
}
const newChildren = newProps.children;
- reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
@@ -3012,7 +2832,7 @@ let hasWarnedAboutUsingContextAsConsumer = false;
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
let context: ReactContext = workInProgress.type;
// The logic below for Context differs depending on PROD or DEV mode. In
@@ -3054,7 +2874,7 @@ function updateContextConsumer(
}
}
- prepareToReadContext(workInProgress, renderExpirationTime);
+ prepareToReadContext(workInProgress, renderLanes);
const newValue = readContext(context, newProps.unstable_observedBits);
let newChildren;
if (__DEV__) {
@@ -3068,15 +2888,11 @@ function updateContextConsumer(
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
- reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
+ reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}
-function updateFundamentalComponent(
- current,
- workInProgress,
- renderExpirationTime,
-) {
+function updateFundamentalComponent(current, workInProgress, renderLanes) {
const fundamentalImpl = workInProgress.type.impl;
if (fundamentalImpl.reconcileChildren === false) {
return null;
@@ -3084,25 +2900,15 @@ function updateFundamentalComponent(
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
-function updateScopeComponent(current, workInProgress, renderExpirationTime) {
+function updateScopeComponent(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
- reconcileChildren(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
+ reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
@@ -3113,7 +2919,7 @@ export function markWorkInProgressReceivedUpdate() {
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
// Reuse previous dependencies
@@ -3125,16 +2931,10 @@ function bailoutOnAlreadyFinishedWork(
stopProfilerTimerIfRunning(workInProgress);
}
- const updateExpirationTime = workInProgress.expirationTime_opaque;
- if (
- !isSameExpirationTime(updateExpirationTime, (NoWork: ExpirationTimeOpaque))
- ) {
- markUnprocessedUpdateTime(updateExpirationTime);
- }
+ markSkippedUpdateLanes(workInProgress.lanes);
// Check if the children have any pending work.
- const childExpirationTime = workInProgress.childExpirationTime_opaque;
- if (!isSameOrHigherPriority(childExpirationTime, renderExpirationTime)) {
+ if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
@@ -3213,9 +3013,9 @@ function remountFiber(
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): Fiber | null {
- const updateExpirationTime = workInProgress.expirationTime_opaque;
+ const updateLanes = workInProgress.lanes;
if (__DEV__) {
if (workInProgress._debugNeedsRemount && current !== null) {
@@ -3229,7 +3029,7 @@ function beginWork(
workInProgress.pendingProps,
workInProgress._debugOwner || null,
workInProgress.mode,
- workInProgress.expirationTime_opaque,
+ workInProgress.lanes,
),
);
}
@@ -3248,9 +3048,7 @@ function beginWork(
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
- } else if (
- !isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)
- ) {
+ } else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
@@ -3284,9 +3082,9 @@ function beginWork(
case Profiler:
if (enableProfilerTimer) {
// Profiler should only call onRender when one of its descendants actually rendered.
- const hasChildWork = isSameOrHigherPriority(
- workInProgress.childExpirationTime_opaque,
- renderExpirationTime,
+ const hasChildWork = includesSomeLane(
+ renderLanes,
+ workInProgress.childLanes,
);
if (hasChildWork) {
workInProgress.effectTag |= Update;
@@ -3321,20 +3119,14 @@ function beginWork(
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
- const primaryChildExpirationTime =
- primaryChildFragment.childExpirationTime_opaque;
- if (
- isSameOrHigherPriority(
- primaryChildExpirationTime,
- renderExpirationTime,
- )
- ) {
+ const primaryChildLanes = primaryChildFragment.childLanes;
+ if (includesSomeLane(renderLanes, primaryChildLanes)) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
} else {
// The primary child fragment does not have pending work marked
@@ -3348,7 +3140,7 @@ function beginWork(
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
@@ -3370,9 +3162,9 @@ function beginWork(
const didSuspendBefore =
(current.effectTag & DidCapture) !== NoEffect;
- const hasChildWork = isSameOrHigherPriority(
- workInProgress.childExpirationTime_opaque,
- renderExpirationTime,
+ const hasChildWork = includesSomeLane(
+ renderLanes,
+ workInProgress.childLanes,
);
if (didSuspendBefore) {
@@ -3385,7 +3177,7 @@ function beginWork(
return updateSuspenseListComponent(
current,
workInProgress,
- renderExpirationTime,
+ renderLanes,
);
}
// If none of the children had any work, that means that none of
@@ -3426,19 +3218,11 @@ function beginWork(
// TODO: Probably should refactor `beginWork` to split the bailout
// path from the normal path. I'm tempted to do a labeled break here
// but I won't :)
- workInProgress.expirationTime_opaque = NoWork;
- return updateOffscreenComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ workInProgress.lanes = NoLanes;
+ return updateOffscreenComponent(current, workInProgress, renderLanes);
}
}
- return bailoutOnAlreadyFinishedWork(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
@@ -3451,11 +3235,7 @@ function beginWork(
}
// Before entering the begin phase, clear pending update priority.
- // TODO: This assumes that we're about to evaluate the component and process
- // the update queue. However, there's an exception: SimpleMemoComponent
- // sometimes bails out later in the begin phase. This indicates that we should
- // move this assignment out of the common path and into each branch.
- workInProgress.expirationTime_opaque = NoWork;
+ workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: {
@@ -3463,7 +3243,7 @@ function beginWork(
current,
workInProgress,
workInProgress.type,
- renderExpirationTime,
+ renderLanes,
);
}
case LazyComponent: {
@@ -3472,8 +3252,8 @@ function beginWork(
current,
workInProgress,
elementType,
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
);
}
case FunctionComponent: {
@@ -3488,7 +3268,7 @@ function beginWork(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
}
case ClassComponent: {
@@ -3503,27 +3283,19 @@ function beginWork(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
}
case HostRoot:
- return updateHostRoot(current, workInProgress, renderExpirationTime);
+ return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
- return updateHostComponent(current, workInProgress, renderExpirationTime);
+ return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
- return updateSuspenseComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
- return updatePortalComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
@@ -3536,27 +3308,19 @@ function beginWork(
workInProgress,
type,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
}
case Fragment:
- return updateFragment(current, workInProgress, renderExpirationTime);
+ return updateFragment(current, workInProgress, renderLanes);
case Mode:
- return updateMode(current, workInProgress, renderExpirationTime);
+ return updateMode(current, workInProgress, renderLanes);
case Profiler:
- return updateProfiler(current, workInProgress, renderExpirationTime);
+ return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
- return updateContextProvider(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
- return updateContextConsumer(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
@@ -3581,8 +3345,8 @@ function beginWork(
workInProgress,
type,
resolvedProps,
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
);
}
case SimpleMemoComponent: {
@@ -3591,8 +3355,8 @@ function beginWork(
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
- updateExpirationTime,
- renderExpirationTime,
+ updateLanes,
+ renderLanes,
);
}
case IncompleteClassComponent: {
@@ -3607,33 +3371,21 @@ function beginWork(
workInProgress,
Component,
resolvedProps,
- renderExpirationTime,
+ renderLanes,
);
}
case SuspenseListComponent: {
- return updateSuspenseListComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
- return updateFundamentalComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateFundamentalComponent(current, workInProgress, renderLanes);
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
- return updateScopeComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
@@ -3641,29 +3393,15 @@ function beginWork(
if (enableBlocksAPI) {
const block = workInProgress.type;
const props = workInProgress.pendingProps;
- return updateBlock(
- current,
- workInProgress,
- block,
- props,
- renderExpirationTime,
- );
+ return updateBlock(current, workInProgress, block, props, renderLanes);
}
break;
}
case OffscreenComponent: {
- return updateOffscreenComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case LegacyHiddenComponent: {
- return updateLegacyHiddenComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
+ return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
}
}
invariant(
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js
index b568278d452be..20e544b00a056 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.new.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.new.js
@@ -8,7 +8,7 @@
*/
import type {Fiber} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import type {UpdateQueue} from './ReactUpdateQueue.new';
import * as React from 'react';
@@ -40,7 +40,7 @@ import {
initializeUpdateQueue,
cloneUpdateQueue,
} from './ReactUpdateQueue.new';
-import {NoWork, isSameExpirationTime} from './ReactFiberExpirationTime.new';
+import {NoLanes} from './ReactFiberLane';
import {
cacheContext,
getMaskedContext,
@@ -51,7 +51,7 @@ import {
import {readContext} from './ReactFiberNewContext.new';
import {
requestEventTime,
- requestUpdateExpirationTime,
+ requestUpdateLane,
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop.new';
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
@@ -177,12 +177,7 @@ export function applyDerivedStateFromProps(
// Once the update queue is empty, persist the derived state onto the
// base state.
- if (
- isSameExpirationTime(
- workInProgress.expirationTime_opaque,
- (NoWork: ExpirationTimeOpaque),
- )
- ) {
+ if (workInProgress.lanes === NoLanes) {
// Queue is always non-null for classes
const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
updateQueue.baseState = memoizedState;
@@ -195,9 +190,9 @@ const classComponentUpdater = {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
+ const lane = requestUpdateLane(fiber, suspenseConfig);
- const update = createUpdate(eventTime, expirationTime, suspenseConfig);
+ const update = createUpdate(eventTime, lane, suspenseConfig);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
@@ -207,15 +202,15 @@ const classComponentUpdater = {
}
enqueueUpdate(fiber, update);
- scheduleUpdateOnFiber(fiber, expirationTime);
+ scheduleUpdateOnFiber(fiber, lane);
},
enqueueReplaceState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
+ const lane = requestUpdateLane(fiber, suspenseConfig);
- const update = createUpdate(eventTime, expirationTime, suspenseConfig);
+ const update = createUpdate(eventTime, lane, suspenseConfig);
update.tag = ReplaceState;
update.payload = payload;
@@ -227,15 +222,15 @@ const classComponentUpdater = {
}
enqueueUpdate(fiber, update);
- scheduleUpdateOnFiber(fiber, expirationTime);
+ scheduleUpdateOnFiber(fiber, lane);
},
enqueueForceUpdate(inst, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
+ const lane = requestUpdateLane(fiber, suspenseConfig);
- const update = createUpdate(eventTime, expirationTime, suspenseConfig);
+ const update = createUpdate(eventTime, lane, suspenseConfig);
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
@@ -246,7 +241,7 @@ const classComponentUpdater = {
}
enqueueUpdate(fiber, update);
- scheduleUpdateOnFiber(fiber, expirationTime);
+ scheduleUpdateOnFiber(fiber, lane);
},
};
@@ -771,7 +766,7 @@ function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
@@ -823,7 +818,7 @@ function mountClassInstance(
}
}
- processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
+ processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
@@ -848,12 +843,7 @@ function mountClassInstance(
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
- processUpdateQueue(
- workInProgress,
- newProps,
- instance,
- renderExpirationTime,
- );
+ processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
}
@@ -866,7 +856,7 @@ function resumeMountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): boolean {
const instance = workInProgress.stateNode;
@@ -917,7 +907,7 @@ function resumeMountClassInstance(
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
- processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
+ processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;
if (
oldProps === newProps &&
@@ -1001,7 +991,7 @@ function updateClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): boolean {
const instance = workInProgress.stateNode;
@@ -1058,7 +1048,7 @@ function updateClassInstance(
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
- processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
+ processUpdateQueue(workInProgress, newProps, instance, renderLanes);
newState = workInProgress.memoizedState;
if (
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
index 8e71d3a99bef6..91e74031dded8 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
@@ -17,7 +17,7 @@ import type {
} from './ReactFiberHostConfig';
import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {UpdateQueue} from './ReactUpdateQueue.new';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
@@ -516,7 +516,7 @@ function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
- committedExpirationTime: ExpirationTimeOpaque,
+ committedLanes: Lanes,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
index e2280673232ba..e420bb6916c70 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
@@ -8,7 +8,7 @@
*/
import type {Fiber} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import type {
ReactFundamentalComponentInstance,
ReactScopeInstance,
@@ -133,10 +133,10 @@ import {
renderDidSuspend,
renderDidSuspendDelayIfPossible,
renderHasNotSuspendedYet,
- popRenderExpirationTime,
+ popRenderLanes,
} from './ReactFiberWorkLoop.new';
import {createFundamentalStateInstance} from './ReactFiberFundamental.new';
-import {Never, isSameOrHigherPriority} from './ReactFiberExpirationTime.new';
+import {OffscreenLane} from './ReactFiberLane';
import {resetChildFibers} from './ReactChildFiber.new';
import {updateDeprecatedEventListeners} from './ReactFiberDeprecatedEvents.new';
import {createScopeInstance} from './ReactFiberScope.new';
@@ -644,7 +644,7 @@ function cutOffTailIfNeeded(
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
@@ -857,7 +857,7 @@ function completeWork(
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
- markSpawnedWork((Never: ExpirationTimeOpaque));
+ markSpawnedWork(OffscreenLane);
}
return null;
} else {
@@ -882,7 +882,7 @@ function completeWork(
if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
// Something suspended. Re-render with the fallback children.
- workInProgress.expirationTime_opaque = renderExpirationTime;
+ workInProgress.lanes = renderLanes;
// Do not reset the effect list.
return workInProgress;
}
@@ -1051,7 +1051,7 @@ function completeWork(
}
workInProgress.lastEffect = renderState.lastEffect;
// Reset the child fibers to their original state.
- resetChildFibers(workInProgress, renderExpirationTime);
+ resetChildFibers(workInProgress, renderLanes);
// Set up the Suspense Context to force suspense and immediately
// rerender the children.
@@ -1111,10 +1111,7 @@ function completeWork(
// the expiration.
now() * 2 - renderState.renderingStartTime >
renderState.tailExpiration &&
- !isSameOrHigherPriority(
- (Never: ExpirationTimeOpaque),
- renderExpirationTime,
- )
+ renderLanes !== OffscreenLane
) {
// We have now passed our CPU deadline and we'll just give up further
// attempts to render the main content and only render fallbacks.
@@ -1129,9 +1126,9 @@ function completeWork(
// them, then they really have the same priority as this render.
// So we'll pick it back up the very next render pass once we've had
// an opportunity to yield for paint.
- workInProgress.expirationTime_opaque = renderExpirationTime;
+ workInProgress.lanes = renderLanes;
if (enableSchedulerTracing) {
- markSpawnedWork(renderExpirationTime);
+ markSpawnedWork(renderLanes);
}
}
}
@@ -1297,7 +1294,7 @@ function completeWork(
break;
case OffscreenComponent:
case LegacyHiddenComponent: {
- popRenderExpirationTime(workInProgress);
+ popRenderLanes(workInProgress);
if (current !== null) {
const nextState: OffscreenState | null = workInProgress.memoizedState;
const prevState: OffscreenState | null = current.memoizedState;
diff --git a/packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js b/packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js
index f77179fdb105c..26e055718cf84 100644
--- a/packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js
+++ b/packages/react-reconciler/src/ReactFiberDeprecatedEvents.new.js
@@ -19,7 +19,7 @@ import {
DEPRECATED_mountResponderInstance,
DEPRECATED_unmountResponderInstance,
} from './ReactFiberHostConfig';
-import {NoWork} from './ReactFiberExpirationTime.new';
+import {NoLanes} from './ReactFiberLane';
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
@@ -154,7 +154,7 @@ export function updateDeprecatedEventListeners(
if (listeners != null) {
if (dependencies === null) {
dependencies = fiber.dependencies_new = {
- expirationTime: NoWork,
+ lanes: NoLanes,
firstContext: null,
responders: new Map(),
};
diff --git a/packages/react-reconciler/src/ReactFiberExpirationTime.new.js b/packages/react-reconciler/src/ReactFiberExpirationTime.new.js
deleted file mode 100644
index d8d3cfd69d5ab..0000000000000
--- a/packages/react-reconciler/src/ReactFiberExpirationTime.new.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {ReactPriorityLevel} from './ReactInternalTypes';
-
-import {MAX_SIGNED_31_BIT_INT} from './MaxInts';
-
-import {
- ImmediatePriority,
- UserBlockingPriority,
- NormalPriority,
- IdlePriority,
-} from './SchedulerWithReactIntegration.new';
-
-export opaque type ExpirationTimeOpaque = number;
-
-export const NoWork: ExpirationTimeOpaque = 0;
-// TODO: Think of a better name for Never. The key difference with Idle is that
-// Never work can be committed in an inconsistent state without tearing the UI.
-// The main example is offscreen content, like a hidden subtree. So one possible
-// name is Offscreen. However, it also includes dehydrated Suspense boundaries,
-// which are inconsistent in the sense that they haven't finished yet, but
-// aren't visibly inconsistent because the server rendered HTML matches what the
-// hydrated tree would look like.
-export const Never: ExpirationTimeOpaque = 1;
-// Idle is slightly higher priority than Never. It must completely finish in
-// order to be consistent.
-export const Idle: ExpirationTimeOpaque = 2;
-// Continuous Hydration is slightly higher than Idle and is used to increase
-// priority of hover targets.
-export const ContinuousHydration: ExpirationTimeOpaque = 3;
-export const LongTransition: ExpirationTimeOpaque = 49999;
-export const ShortTransition: ExpirationTimeOpaque = 99999;
-export const DefaultUpdateTime: ExpirationTimeOpaque = 1073741296;
-export const UserBlockingUpdateTime: ExpirationTimeOpaque = 1073741761;
-export const Sync: ExpirationTimeOpaque = MAX_SIGNED_31_BIT_INT;
-export const Batched: ExpirationTimeOpaque = Sync - 1;
-
-// Accounts for -1 trick to bump updates into a different batch
-const ADJUSTMENT_OFFSET = 5;
-
-export function inferPriorityFromExpirationTime(
- expirationTime: ExpirationTimeOpaque,
-): ReactPriorityLevel {
- if (expirationTime >= Batched - ADJUSTMENT_OFFSET) {
- return ImmediatePriority;
- }
- if (expirationTime >= UserBlockingUpdateTime - ADJUSTMENT_OFFSET) {
- return UserBlockingPriority;
- }
- if (expirationTime >= LongTransition - ADJUSTMENT_OFFSET) {
- return NormalPriority;
- }
-
- // TODO: Handle LowPriority. Maybe should give it NormalPriority since Idle is
- // very agressively deprioritized.
-
- // Assume anything lower has idle priority
- return IdlePriority;
-}
-
-export function isSameOrHigherPriority(
- a: ExpirationTimeOpaque,
- b: ExpirationTimeOpaque,
-) {
- return a >= b;
-}
-
-export function isSameExpirationTime(
- a: ExpirationTimeOpaque,
- b: ExpirationTimeOpaque,
-) {
- return a === b;
-}
-
-export function bumpPriorityHigher(
- a: ExpirationTimeOpaque,
-): ExpirationTimeOpaque {
- return a + 1;
-}
-
-export function bumpPriorityLower(
- a: ExpirationTimeOpaque,
-): ExpirationTimeOpaque {
- return a - 1;
-}
diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js
index 818a7e529bff5..1aa40b754cfe9 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.new.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.new.js
@@ -16,7 +16,7 @@ import type {
ReactEventResponderListener,
} from 'shared/ReactTypes';
import type {Fiber, Dispatcher} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes, Lane} from './ReactFiberLane';
import type {HookEffectTag} from './ReactHookEffectTags';
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
import type {ReactPriorityLevel} from './ReactInternalTypes';
@@ -25,14 +25,17 @@ import type {OpaqueIDType} from './ReactFiberHostConfig';
import ReactSharedInternals from 'shared/ReactSharedInternals';
-import {markRootExpiredAtTime} from './ReactFiberRoot.new';
-import {
- NoWork,
- Sync,
- isSameOrHigherPriority,
- isSameExpirationTime,
-} from './ReactFiberExpirationTime.new';
import {NoMode, BlockingMode} from './ReactTypeOfMode';
+import {
+ NoLane,
+ NoLanes,
+ includesSomeLane,
+ isSubsetOfLanes,
+ combineLanes,
+ removeLanes,
+ markRootExpired,
+ markRootMutableRead,
+} from './ReactFiberLane';
import {readContext} from './ReactFiberNewContext.new';
import {createDeprecatedResponderListener} from './ReactFiberDeprecatedEvents.new';
import {
@@ -47,13 +50,13 @@ import {
import {
getWorkInProgressRoot,
scheduleUpdateOnFiber,
- requestUpdateExpirationTime,
+ requestUpdateLane,
requestEventTime,
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDev,
warnIfNotScopedWithMatchingAct,
markRenderEventTimeAndConfig,
- markUnprocessedUpdateTime,
+ markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.new';
import invariant from 'shared/invariant';
@@ -74,10 +77,8 @@ import {
makeOpaqueHydratingObject,
} from './ReactFiberHostConfig';
import {
- getLastPendingExpirationTime,
getWorkInProgressVersion,
markSourceAsDirty,
- setPendingExpirationTime,
setWorkInProgressVersion,
warnAboutMultipleRenderersDEV,
} from './ReactMutableSource.new';
@@ -89,7 +90,7 @@ type Update = {|
// TODO: Temporary field. Will remove this by storing a map of
// transition -> start time on the root.
eventTime: number,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane,
suspenseConfig: null | SuspenseConfig,
action: A,
eagerReducer: ((S, A) => S) | null,
@@ -156,7 +157,7 @@ type BasicStateAction = (S => S) | S;
type Dispatch = A => void;
// These are set right before calling the component.
-let renderExpirationTime: ExpirationTimeOpaque = NoWork;
+let renderLanes: Lanes = NoLanes;
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber = (null: any);
@@ -347,9 +348,9 @@ export function renderWithHooks(
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
- nextRenderExpirationTime: ExpirationTimeOpaque,
+ nextRenderLanes: Lanes,
): any {
- renderExpirationTime = nextRenderExpirationTime;
+ renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
if (__DEV__) {
@@ -365,7 +366,7 @@ export function renderWithHooks(
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
- workInProgress.expirationTime_opaque = NoWork;
+ workInProgress.lanes = NoLanes;
// The following should have already been reset
// currentHook = null;
@@ -454,7 +455,7 @@ export function renderWithHooks(
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
- renderExpirationTime = NoWork;
+ renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
@@ -480,13 +481,11 @@ export function renderWithHooks(
export function bailoutHooks(
current: Fiber,
workInProgress: Fiber,
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
) {
workInProgress.updateQueue = current.updateQueue;
workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);
- if (isSameOrHigherPriority(expirationTime, current.expirationTime_opaque)) {
- current.expirationTime_opaque = NoWork;
- }
+ current.lanes = removeLanes(current.lanes, lanes);
}
export function resetHooksAfterThrow(): void {
@@ -514,7 +513,7 @@ export function resetHooksAfterThrow(): void {
didScheduleRenderPhaseUpdate = false;
}
- renderExpirationTime = NoWork;
+ renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
@@ -708,15 +707,15 @@ function updateReducer(
let update = first;
do {
const suspenseConfig = update.suspenseConfig;
- const updateExpirationTime = update.expirationTime;
+ const updateLane = update.lane;
const updateEventTime = update.eventTime;
- if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
+ if (!includesSomeLane(renderLanes, updateLane) && updateLane !== NoLane) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update = {
eventTime: updateEventTime,
- expirationTime: updateExpirationTime,
+ lane: updateLane,
suspenseConfig: suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
@@ -730,22 +729,20 @@ function updateReducer(
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
- if (
- !isSameOrHigherPriority(
- currentlyRenderingFiber.expirationTime_opaque,
- updateExpirationTime,
- )
- ) {
- currentlyRenderingFiber.expirationTime_opaque = updateExpirationTime;
- markUnprocessedUpdateTime(updateExpirationTime);
- }
+ // TODO: Don't need to accumulate this. Instead, we can remove
+ // renderLanes from the original lanes.
+ currentlyRenderingFiber.lanes = combineLanes(
+ currentlyRenderingFiber.lanes,
+ updateLane,
+ );
+ markSkippedUpdateLanes(updateLane);
} else {
// This update does have sufficient priority.
if (newBaseQueueLast !== null) {
const clone: Update = {
eventTime: updateEventTime,
- expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
+ lane: NoLane, // This update is going to be committed so we never want uncommit it.
suspenseConfig: update.suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
@@ -884,24 +881,14 @@ function readFromUnsubcribedMutableSource(
if (currentRenderVersion !== null) {
isSafeToReadFromSource = currentRenderVersion === version;
} else {
- // If there's no version, then we should fallback to checking the update time.
- const pendingExpirationTime = getLastPendingExpirationTime(root);
+ // If there's no version, then we should fallback to checking the lanes.
- if (
- isSameExpirationTime(
- pendingExpirationTime,
- (NoWork: ExpirationTimeOpaque),
- )
- ) {
- isSafeToReadFromSource = true;
- } else {
- // If the source has pending updates, we can use the current render's expiration
- // time to determine if it's safe to read again from the source.
- isSafeToReadFromSource = isSameOrHigherPriority(
- pendingExpirationTime,
- renderExpirationTime,
- );
- }
+ // If the source has pending updates, we can use the current render's expiration
+ // time to determine if it's safe to read again from the source.
+ isSafeToReadFromSource = isSubsetOfLanes(
+ renderLanes,
+ root.mutableReadLanes,
+ );
if (isSafeToReadFromSource) {
// If it's safe to read from this source during the current render,
@@ -994,19 +981,16 @@ function useMutableSource(
setSnapshot(maybeNewSnapshot);
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(
- fiber,
- suspenseConfig,
- );
- setPendingExpirationTime(root, expirationTime);
+ const lane = requestUpdateLane(fiber, suspenseConfig);
+ markRootMutableRead(root, lane);
// If the source mutated between render and now,
// there may be state updates already scheduled from the old getSnapshot.
// Those updates should not commit without this value.
// There is no mechanism currently to associate these updates though,
// so for now we fall back to synchronously flushing all pending updates.
- // TODO: Improve this later.
- markRootExpiredAtTime(root, getLastPendingExpirationTime(root));
+ // TODO: This should entangle the lanes instead of expiring everything.
+ markRootExpired(root, root.mutableReadLanes);
}
}
}, [getSnapshot, source, subscribe]);
@@ -1022,12 +1006,9 @@ function useMutableSource(
// Record a pending mutable source update with the same expiration time.
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(
- fiber,
- suspenseConfig,
- );
+ const lane = requestUpdateLane(fiber, suspenseConfig);
- setPendingExpirationTime(root, expirationTime);
+ markRootMutableRead(root, lane);
} catch (error) {
// A selector might throw after a source mutation.
// e.g. it might try to read from a part of the store that no longer exists.
@@ -1646,11 +1627,11 @@ function dispatchAction(
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(fiber, suspenseConfig);
+ const lane = requestUpdateLane(fiber, suspenseConfig);
const update: Update = {
eventTime,
- expirationTime,
+ lane,
suspenseConfig,
action,
eagerReducer: null,
@@ -1678,18 +1659,10 @@ function dispatchAction(
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
- update.expirationTime = renderExpirationTime;
} else {
if (
- isSameExpirationTime(
- fiber.expirationTime_opaque,
- (NoWork: ExpirationTimeOpaque),
- ) &&
- (alternate === null ||
- isSameExpirationTime(
- alternate.expirationTime_opaque,
- (NoWork: ExpirationTimeOpaque),
- ))
+ fiber.lanes === NoLanes &&
+ (alternate === null || alternate.lanes === NoLanes)
) {
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
@@ -1733,7 +1706,7 @@ function dispatchAction(
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
- scheduleUpdateOnFiber(fiber, expirationTime);
+ scheduleUpdateOnFiber(fiber, lane);
}
}
diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.new.js b/packages/react-reconciler/src/ReactFiberHotReloading.new.js
index 2c834382eea92..21819bf9edbc1 100644
--- a/packages/react-reconciler/src/ReactFiberHotReloading.new.js
+++ b/packages/react-reconciler/src/ReactFiberHotReloading.new.js
@@ -12,7 +12,6 @@ import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
import type {Instance} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
import {
flushSync,
@@ -21,7 +20,7 @@ import {
} from './ReactFiberWorkLoop.new';
import {updateContainer, syncUpdates} from './ReactFiberReconciler.new';
import {emptyContextObject} from './ReactFiberContext.new';
-import {Sync} from './ReactFiberExpirationTime.new';
+import {SyncLane} from './ReactFiberLane';
import {
ClassComponent,
FunctionComponent,
@@ -320,7 +319,7 @@ function scheduleFibersWithFamiliesRecursively(
fiber._debugNeedsRemount = true;
}
if (needsRemount || needsRender) {
- scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
+ scheduleUpdateOnFiber(fiber, SyncLane);
}
if (child !== null && !needsRemount) {
scheduleFibersWithFamiliesRecursively(
diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js
index f1ed2e73cccdb..3f4c83da829ba 100644
--- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js
+++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js
@@ -55,7 +55,7 @@ import {
didNotFindHydratableSuspenseInstance,
} from './ReactFiberHostConfig';
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
-import {Never} from './ReactFiberExpirationTime.new';
+import {OffscreenLane} from './ReactFiberLane';
// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
@@ -231,7 +231,7 @@ function tryHydrate(fiber, nextInstance) {
if (suspenseInstance !== null) {
const suspenseState: SuspenseState = {
dehydrated: suspenseInstance,
- retryTime: Never,
+ retryLane: OffscreenLane,
};
fiber.memoizedState = suspenseState;
// Store the dehydrated fragment as a child fiber.
diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js
index 4e389fe737b80..1a70dc493cecf 100644
--- a/packages/react-reconciler/src/ReactFiberLane.js
+++ b/packages/react-reconciler/src/ReactFiberLane.js
@@ -7,16 +7,688 @@
* @flow
*/
-export opaque type LanePriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
-
-export const SyncLanePriority: LanePriority = 10;
-export const SyncBatchedLanePriority: LanePriority = 9;
-export const InputDiscreteLanePriority: LanePriority = 8;
-export const InputContinuousLanePriority: LanePriority = 7;
-export const DefaultLanePriority: LanePriority = 6;
-export const TransitionShortLanePriority: LanePriority = 5;
-export const TransitionLongLanePriority: LanePriority = 4;
-export const HydrationContinuousLanePriority: LanePriority = 3;
-export const IdleLanePriority: LanePriority = 2;
-export const OffscreenLanePriority: LanePriority = 1;
-export const NoLanePriority: LanePriority = 0;
+import type {FiberRoot, ReactPriorityLevel} from './ReactInternalTypes';
+
+export opaque type LanePriority =
+ | 0
+ | 1
+ | 2
+ | 3
+ | 4
+ | 5
+ | 6
+ | 7
+ | 8
+ | 9
+ | 10
+ | 11
+ | 12
+ | 13
+ | 14
+ | 15
+ | 16;
+export opaque type Lanes = number;
+export opaque type Lane = number;
+
+export opaque type LaneMap = Array;
+
+import invariant from 'shared/invariant';
+
+import {
+ ImmediatePriority as ImmediateSchedulerPriority,
+ UserBlockingPriority as UserBlockingSchedulerPriority,
+ NormalPriority as NormalSchedulerPriority,
+ LowPriority as LowSchedulerPriority,
+ IdlePriority as IdleSchedulerPriority,
+ NoPriority as NoSchedulerPriority,
+} from './SchedulerWithReactIntegration.new';
+
+export const SyncLanePriority: LanePriority = 16;
+const SyncBatchedLanePriority: LanePriority = 15;
+
+const InputDiscreteHydrationLanePriority: LanePriority = 14;
+export const InputDiscreteLanePriority: LanePriority = 13;
+
+const InputContinuousHydrationLanePriority: LanePriority = 12;
+const InputContinuousLanePriority: LanePriority = 11;
+
+const DefaultHydrationLanePriority: LanePriority = 10;
+const DefaultLanePriority: LanePriority = 9;
+
+const TransitionShortHydrationLanePriority: LanePriority = 8;
+export const TransitionShortLanePriority: LanePriority = 7;
+
+const TransitionLongHydrationLanePriority: LanePriority = 6;
+export const TransitionLongLanePriority: LanePriority = 5;
+
+const SelectiveHydrationLanePriority: LanePriority = 4;
+
+const IdleHydrationLanePriority: LanePriority = 3;
+const IdleLanePriority: LanePriority = 2;
+
+const OffscreenLanePriority: LanePriority = 1;
+
+const NoLanePriority: LanePriority = 0;
+
+const TotalLanes = 31;
+
+export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
+export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
+
+export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
+const SyncUpdateRangeEnd = 1;
+export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;
+const SyncBatchedUpdateRangeEnd = 2;
+
+export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
+const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011100;
+const InputDiscreteUpdateRangeStart = 3;
+const InputDiscreteUpdateRangeEnd = 5;
+
+const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
+const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011100000;
+const InputContinuousUpdateRangeStart = 6;
+const InputContinuousUpdateRangeEnd = 8;
+
+export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
+const DefaultLanes: Lanes = /* */ 0b0000000000000000011111100000000;
+const DefaultUpdateRangeStart = 9;
+const DefaultUpdateRangeEnd = 14;
+
+const TransitionShortHydrationLane: Lane = /* */ 0b0000000000000000100000000000000;
+const TransitionShortLanes: Lanes = /* */ 0b0000000000011111100000000000000;
+const TransitionShortUpdateRangeStart = 15;
+const TransitionShortUpdateRangeEnd = 20;
+
+const TransitionLongHydrationLane: Lane = /* */ 0b0000000000100000000000000000000;
+const TransitionLongLanes: Lanes = /* */ 0b0000011111100000000000000000000;
+const TransitionLongUpdateRangeStart = 21;
+const TransitionLongUpdateRangeEnd = 26;
+
+export const SelectiveHydrationLane: Lane = /* */ 0b0000110000000000000000000000000;
+const SelectiveHydrationRangeEnd = 27;
+
+// Includes all non-Idle updates
+const UpdateRangeEnd = 27;
+const NonIdleLanes = /* */ 0b0000111111111111111111111111111;
+
+export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
+const IdleLanes: Lanes = /* */ 0b0111000000000000000000000000000;
+const IdleUpdateRangeStart = 28;
+const IdleUpdateRangeEnd = 30;
+
+export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
+
+// "Registers" used to "return" multiple values
+// Used by getHighestPriorityLanes and getNextLanes:
+let return_highestLanePriority: LanePriority = DefaultLanePriority;
+let return_updateRangeEnd: number = -1;
+
+function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
+ if ((SyncLane & lanes) !== NoLanes) {
+ return_highestLanePriority = SyncLanePriority;
+ return_updateRangeEnd = SyncUpdateRangeEnd;
+ return SyncLane;
+ }
+ if ((SyncBatchedLane & lanes) !== NoLanes) {
+ return_highestLanePriority = SyncBatchedLanePriority;
+ return_updateRangeEnd = SyncBatchedUpdateRangeEnd;
+ return SyncBatchedLane;
+ }
+ const inputDiscreteLanes = InputDiscreteLanes & lanes;
+ if (inputDiscreteLanes !== NoLanes) {
+ if (inputDiscreteLanes & InputDiscreteHydrationLane) {
+ return_highestLanePriority = InputDiscreteHydrationLanePriority;
+ return_updateRangeEnd = InputDiscreteUpdateRangeStart;
+ return InputDiscreteHydrationLane;
+ } else {
+ return_highestLanePriority = InputDiscreteLanePriority;
+ return_updateRangeEnd = InputDiscreteUpdateRangeEnd;
+ return inputDiscreteLanes;
+ }
+ }
+ const inputContinuousLanes = InputContinuousLanes & lanes;
+ if (inputContinuousLanes !== NoLanes) {
+ if (inputContinuousLanes & InputContinuousHydrationLane) {
+ return_highestLanePriority = InputContinuousHydrationLanePriority;
+ return_updateRangeEnd = InputContinuousUpdateRangeStart;
+ return InputContinuousHydrationLane;
+ } else {
+ return_highestLanePriority = InputContinuousLanePriority;
+ return_updateRangeEnd = InputContinuousUpdateRangeEnd;
+ return inputContinuousLanes;
+ }
+ }
+ const defaultLanes = DefaultLanes & lanes;
+ if (defaultLanes !== NoLanes) {
+ if (defaultLanes & DefaultHydrationLane) {
+ return_highestLanePriority = DefaultHydrationLanePriority;
+ return_updateRangeEnd = DefaultUpdateRangeStart;
+ return DefaultHydrationLane;
+ } else {
+ return_highestLanePriority = DefaultLanePriority;
+ return_updateRangeEnd = DefaultUpdateRangeEnd;
+ return defaultLanes;
+ }
+ }
+ const transitionShortLanes = TransitionShortLanes & lanes;
+ if (transitionShortLanes !== NoLanes) {
+ if (transitionShortLanes & TransitionShortHydrationLane) {
+ return_highestLanePriority = TransitionShortHydrationLanePriority;
+ return_updateRangeEnd = TransitionShortUpdateRangeStart;
+ return TransitionShortHydrationLane;
+ } else {
+ return_highestLanePriority = TransitionShortLanePriority;
+ return_updateRangeEnd = TransitionShortUpdateRangeEnd;
+ return transitionShortLanes;
+ }
+ }
+ const transitionLongLanes = TransitionLongLanes & lanes;
+ if (transitionLongLanes !== NoLanes) {
+ if (transitionLongLanes & TransitionLongHydrationLane) {
+ return_highestLanePriority = TransitionLongHydrationLanePriority;
+ return_updateRangeEnd = TransitionLongUpdateRangeStart;
+ return TransitionLongHydrationLane;
+ } else {
+ return_highestLanePriority = TransitionLongLanePriority;
+ return_updateRangeEnd = TransitionLongUpdateRangeEnd;
+ return transitionLongLanes;
+ }
+ }
+ if (lanes & SelectiveHydrationLane) {
+ return_highestLanePriority = SelectiveHydrationLanePriority;
+ return_updateRangeEnd = SelectiveHydrationRangeEnd;
+ return SelectiveHydrationLane;
+ }
+ const idleLanes = IdleLanes & lanes;
+ if (idleLanes !== NoLanes) {
+ if (idleLanes & IdleHydrationLane) {
+ return_highestLanePriority = IdleHydrationLanePriority;
+ return_updateRangeEnd = IdleUpdateRangeStart;
+ return IdleHydrationLane;
+ } else {
+ return_updateRangeEnd = IdleUpdateRangeEnd;
+ return idleLanes;
+ }
+ }
+ if ((OffscreenLane & lanes) !== NoLanes) {
+ return_highestLanePriority = OffscreenLanePriority;
+ return_updateRangeEnd = TotalLanes;
+ return OffscreenLane;
+ }
+ if (__DEV__) {
+ console.error('Should have found matching lanes. This is a bug in React.');
+ }
+ // This shouldn't be reachable, but as a fallback, return the entire bitmask.
+ return_highestLanePriority = DefaultLanePriority;
+ return_updateRangeEnd = DefaultUpdateRangeEnd;
+ return lanes;
+}
+
+export function schedulerPriorityToLanePriority(
+ schedulerPriorityLevel: ReactPriorityLevel,
+): LanePriority {
+ switch (schedulerPriorityLevel) {
+ case ImmediateSchedulerPriority:
+ return SyncLanePriority;
+ case UserBlockingSchedulerPriority:
+ return InputContinuousLanePriority;
+ case NormalSchedulerPriority:
+ case LowSchedulerPriority:
+ // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
+ return DefaultLanePriority;
+ case IdleSchedulerPriority:
+ return IdleLanePriority;
+ default:
+ return NoLanePriority;
+ }
+}
+
+export function lanePriorityToSchedulerPriority(
+ lanePriority: LanePriority,
+): ReactPriorityLevel {
+ switch (lanePriority) {
+ case SyncLanePriority:
+ case SyncBatchedLanePriority:
+ return ImmediateSchedulerPriority;
+ case InputDiscreteHydrationLanePriority:
+ case InputDiscreteLanePriority:
+ case InputContinuousHydrationLanePriority:
+ case InputContinuousLanePriority:
+ return UserBlockingSchedulerPriority;
+ case DefaultHydrationLanePriority:
+ case DefaultLanePriority:
+ case TransitionShortHydrationLanePriority:
+ case TransitionShortLanePriority:
+ case TransitionLongHydrationLanePriority:
+ case TransitionLongLanePriority:
+ case SelectiveHydrationLanePriority:
+ return NormalSchedulerPriority;
+ case IdleHydrationLanePriority:
+ case IdleLanePriority:
+ case OffscreenLanePriority:
+ return IdleSchedulerPriority;
+ case NoLanePriority:
+ return NoSchedulerPriority;
+ default:
+ invariant(
+ false,
+ 'Invalid update priority: %s. This is a bug in React.',
+ lanePriority,
+ );
+ }
+}
+
+export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
+ // Early bailout if there's no pending work left.
+ const pendingLanes = root.pendingLanes;
+ if (pendingLanes === NoLanes) {
+ return_highestLanePriority = NoLanePriority;
+ return NoLanes;
+ }
+
+ let nextLanes = NoLanes;
+ let nextLanePriority = NoLanePriority;
+ let equalOrHigherPriorityLanes = NoLanes;
+
+ const expiredLanes = root.expiredLanes;
+ const suspendedLanes = root.suspendedLanes;
+ const pingedLanes = root.pingedLanes;
+
+ // Check if any work has expired.
+ if (expiredLanes !== NoLanes) {
+ nextLanes = expiredLanes;
+ nextLanePriority = return_highestLanePriority = SyncLanePriority;
+ equalOrHigherPriorityLanes = (getLowestPriorityLane(nextLanes) << 1) - 1;
+ } else {
+ // Do not work on any idle work until all the non-idle work has finished,
+ // even if the work is suspended.
+ const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
+ if (nonIdlePendingLanes !== NoLanes) {
+ const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
+ if (nonIdleUnblockedLanes !== NoLanes) {
+ nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
+ nextLanePriority = return_highestLanePriority;
+ equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
+ } else {
+ const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
+ if (nonIdlePingedLanes !== NoLanes) {
+ nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
+ nextLanePriority = return_highestLanePriority;
+ equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
+ }
+ }
+ } else {
+ // The only remaining work is Idle.
+ const unblockedLanes = pendingLanes & ~suspendedLanes;
+ if (unblockedLanes !== NoLanes) {
+ nextLanes = getHighestPriorityLanes(unblockedLanes);
+ nextLanePriority = return_highestLanePriority;
+ equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
+ } else {
+ if (pingedLanes !== NoLanes) {
+ nextLanes = getHighestPriorityLanes(pingedLanes);
+ nextLanePriority = return_highestLanePriority;
+ equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
+ }
+ }
+ }
+ }
+
+ if (nextLanes === NoLanes) {
+ // This should only be reachable if we're suspended
+ // TODO: Consider warning in this path if a fallback timer is not scheduled.
+ return NoLanes;
+ }
+
+ // If there are higher priority lanes, we'll include them even if they
+ // are suspended.
+ nextLanes = pendingLanes & equalOrHigherPriorityLanes;
+
+ // If we're already in the middle of a render, switching lanes will interrupt
+ // it and we'll lose our progress. We should only do this if the new lanes are
+ // higher priority.
+ if (
+ wipLanes !== NoLanes &&
+ wipLanes !== nextLanes &&
+ // If we already suspended with a delay, then interrupting is fine. Don't
+ // bother waiting until the root is complete.
+ (wipLanes & suspendedLanes) === NoLanes
+ ) {
+ getHighestPriorityLanes(wipLanes);
+ const wipLanePriority = return_highestLanePriority;
+ if (nextLanePriority <= wipLanePriority) {
+ return wipLanes;
+ } else {
+ return_highestLanePriority = nextLanePriority;
+ }
+ }
+
+ return nextLanes;
+}
+
+// This returns the highest priority pending lanes regardless of whether they
+// are suspended.
+export function getHighestPriorityPendingLanes(root: FiberRoot) {
+ return getHighestPriorityLanes(root.pendingLanes);
+}
+
+export function getLanesToRetrySynchronouslyOnError(root: FiberRoot): Lanes {
+ const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
+ if (everythingButOffscreen !== NoLanes) {
+ return everythingButOffscreen;
+ }
+ if (everythingButOffscreen & OffscreenLane) {
+ return OffscreenLane;
+ }
+ return NoLanes;
+}
+
+export function returnNextLanesPriority() {
+ return return_highestLanePriority;
+}
+export function hasUpdatePriority(lanes: Lanes) {
+ return (lanes & NonIdleLanes) !== NoLanes;
+}
+
+// To ensure consistency across multiple updates in the same event, this should
+// be a pure function, so that it always returns the same lane for given inputs.
+export function findUpdateLane(
+ lanePriority: LanePriority,
+ wipLanes: Lanes,
+): Lane {
+ switch (lanePriority) {
+ case NoLanePriority:
+ break;
+ case SyncLanePriority:
+ return SyncLane;
+ case SyncBatchedLanePriority:
+ return SyncBatchedLane;
+ case InputDiscreteLanePriority: {
+ let lane = findLane(
+ InputDiscreteUpdateRangeStart,
+ UpdateRangeEnd,
+ wipLanes,
+ );
+ if (lane === NoLane) {
+ lane = InputDiscreteHydrationLane;
+ }
+ return lane;
+ }
+ case InputContinuousLanePriority: {
+ let lane = findLane(
+ InputContinuousUpdateRangeStart,
+ UpdateRangeEnd,
+ wipLanes,
+ );
+ if (lane === NoLane) {
+ lane = InputContinuousHydrationLane;
+ }
+ return lane;
+ }
+ case DefaultLanePriority: {
+ let lane = findLane(DefaultUpdateRangeStart, UpdateRangeEnd, wipLanes);
+ if (lane === NoLane) {
+ lane = DefaultHydrationLane;
+ }
+ return lane;
+ }
+ case TransitionShortLanePriority:
+ case TransitionLongLanePriority:
+ // Should be handled by findTransitionLane instead
+ break;
+ case IdleLanePriority:
+ let lane = findLane(IdleUpdateRangeStart, IdleUpdateRangeEnd, IdleLanes);
+ if (lane === NoLane) {
+ lane = IdleHydrationLane;
+ }
+ return lane;
+ default:
+ // The remaining priorities are not valid for updates
+ break;
+ }
+ invariant(
+ false,
+ 'Invalid update priority: %s. This is a bug in React.',
+ lanePriority,
+ );
+}
+
+// To ensure consistency across multiple updates in the same event, this should
+// be pure function, so that it always returns the same lane for given inputs.
+export function findTransitionLane(
+ lanePriority: LanePriority,
+ wipLanes: Lanes,
+ pendingLanes: Lanes,
+): Lane {
+ if (lanePriority === TransitionShortLanePriority) {
+ let lane = findLane(
+ TransitionShortUpdateRangeStart,
+ TransitionShortUpdateRangeEnd,
+ wipLanes | pendingLanes,
+ );
+ if (lane === NoLane) {
+ lane = findLane(
+ TransitionShortUpdateRangeStart,
+ TransitionShortUpdateRangeEnd,
+ wipLanes,
+ );
+ if (lane === NoLane) {
+ lane = TransitionShortHydrationLane;
+ }
+ }
+ return lane;
+ }
+ if (lanePriority === TransitionLongLanePriority) {
+ let lane = findLane(
+ TransitionLongUpdateRangeStart,
+ TransitionLongUpdateRangeEnd,
+ wipLanes | pendingLanes,
+ );
+ if (lane === NoLane) {
+ lane = findLane(
+ TransitionLongUpdateRangeStart,
+ TransitionLongUpdateRangeEnd,
+ wipLanes,
+ );
+ if (lane === NoLane) {
+ lane = TransitionLongHydrationLane;
+ }
+ }
+ return lane;
+ }
+ invariant(
+ false,
+ 'Invalid transition priority: %s. This is a bug in React.',
+ lanePriority,
+ );
+}
+
+function findLane(start, end, skipLanes) {
+ // This finds the first bit between the `start` and `end` positions that isn't
+ // in `skipLanes`.
+ // TODO: This will always favor the rightmost bits. That's usually fine
+ // because any bit that's pending will be part of `skipLanes`, so we'll do our
+ // best to avoid accidental entanglement. However, lanes that are pending
+ // inside an Offscreen tree aren't considered "pending" at the root level. So
+ // they aren't included in `skipLanes`. So we should try not to favor any
+ // particular part of the range, perhaps by incrementing an offset for each
+ // distinct event. Must be the same within a single event, though.
+ const bitsInRange = ((1 << (end - start)) - 1) << start;
+ const possibleBits = bitsInRange & ~skipLanes;
+ const leastSignificantBit = possibleBits & -possibleBits;
+ return leastSignificantBit;
+}
+
+function getLowestPriorityLane(lanes: Lanes): Lane {
+ // This finds the most significant non-zero bit.
+ const index = 31 - clz32(lanes);
+ return index < 0 ? NoLanes : 1 << index;
+}
+
+export function pickArbitraryLane(lanes: Lanes): Lane {
+ return getLowestPriorityLane(lanes);
+}
+
+export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
+ return (a & b) !== NoLanes;
+}
+
+export function isSubsetOfLanes(set: Lanes, subset: Lanes) {
+ return (set & subset) === subset;
+}
+
+export function combineLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
+ return a | b;
+}
+
+export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
+ return set & ~subset;
+}
+
+// Seems redundant, but it changes the type from a single lane (used for
+// updates) to a group of lanes (used for flushing work).
+export function laneToLanes(lane: Lane): Lanes {
+ return lane;
+}
+
+export function higherPriorityLane(a: Lane, b: Lane) {
+ // This works because the bit ranges decrease in priority as you go left.
+ return a !== NoLane && a < b ? a : b;
+}
+
+// export function createLaneMap(initial: T): LaneMap {
+// return new Array(TotalLanes).fill(initial);
+// }
+
+export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
+ root.pendingLanes |= updateLane;
+
+ // TODO: Theoretically, any update to any lane can unblock any other lane. But
+ // it's not practical to try every single possible combination. We need a
+ // heuristic to decide which lanes to attempt to render, and in which batches.
+ // For now, we use the same heuristic as in the old ExpirationTimes model:
+ // retry any lane at equal or lower priority, but don't try updates at higher
+ // priority without also including the lower priority updates. This works well
+ // when considering updates across different priority levels, but isn't
+ // sufficient for updates within the same priority, since we want to treat
+ // those updates as parallel.
+
+ // Unsuspend any update at equal or lower priority.
+ const higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
+ root.suspendedLanes &= higherPriorityLanes;
+ root.pingedLanes &= higherPriorityLanes;
+}
+
+export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
+ root.suspendedLanes |= suspendedLanes;
+ root.pingedLanes &= ~suspendedLanes;
+}
+
+export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
+ root.pingedLanes |= root.suspendedLanes & pingedLanes;
+}
+
+export function markRootExpired(root: FiberRoot, expiredLanes: Lanes) {
+ root.expiredLanes |= expiredLanes & root.pendingLanes;
+}
+
+export function markDiscreteUpdatesExpired(root: FiberRoot) {
+ root.expiredLanes |= InputDiscreteLanes & root.pendingLanes;
+}
+
+export function hasDiscreteLanes(lanes: Lanes) {
+ return (lanes & InputDiscreteLanes) !== NoLanes;
+}
+
+export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
+ root.mutableReadLanes |= updateLane & root.pendingLanes;
+}
+
+export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
+ root.pendingLanes = remainingLanes;
+
+ // Let's try everything again
+ root.suspendedLanes = 0;
+ root.pingedLanes = 0;
+
+ root.expiredLanes &= remainingLanes;
+ root.mutableReadLanes &= remainingLanes;
+}
+
+export function getBumpedLaneForHydration(
+ root: FiberRoot,
+ renderLanes: Lanes,
+): Lane {
+ getHighestPriorityLanes(renderLanes);
+ const highestLanePriority = return_highestLanePriority;
+
+ let lane;
+ switch (highestLanePriority) {
+ case SyncLanePriority:
+ case SyncBatchedLanePriority:
+ lane = NoLane;
+ break;
+ case InputDiscreteHydrationLanePriority:
+ case InputDiscreteLanePriority:
+ lane = InputDiscreteHydrationLane;
+ break;
+ case InputContinuousHydrationLanePriority:
+ case InputContinuousLanePriority:
+ lane = InputContinuousHydrationLane;
+ break;
+ case DefaultHydrationLanePriority:
+ case DefaultLanePriority:
+ lane = DefaultHydrationLane;
+ break;
+ case TransitionShortHydrationLanePriority:
+ case TransitionShortLanePriority:
+ lane = TransitionShortHydrationLane;
+ break;
+ case TransitionLongHydrationLanePriority:
+ case TransitionLongLanePriority:
+ lane = TransitionLongHydrationLane;
+ break;
+ case SelectiveHydrationLanePriority:
+ lane = SelectiveHydrationLane;
+ break;
+ case IdleHydrationLanePriority:
+ case IdleLanePriority:
+ lane = IdleHydrationLane;
+ break;
+ case OffscreenLanePriority:
+ case NoLanePriority:
+ lane = NoLane;
+ break;
+ default:
+ invariant(false, 'Invalid lane: %s. This is a bug in React.', lane);
+ }
+
+ // Check if the lane we chose is suspended. If so, that indicates that we
+ // already attempted and failed to hydrate at that level. Also check if we're
+ // already rendering that lane, which is rare but could happen.
+ if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
+ // Give up trying to hydrate and fall back to client render.
+ return NoLane;
+ }
+
+ return lane;
+}
+
+const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;
+
+// Taken from:
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
+const log = Math.log;
+const LN2 = Math.LN2;
+function clz32Fallback(x) {
+ // Let n be ToUint32(x).
+ // Let p be the number of leading zero bits in
+ // the 32-bit binary representation of n.
+ // Return p.
+ const asUint = x >>> 0;
+ if (asUint === 0) {
+ return 32;
+ }
+ return (31 - ((log(asUint) / LN2) | 0)) | 0; // the "| 0" acts like math.floor
+}
diff --git a/packages/react-reconciler/src/ReactFiberNewContext.new.js b/packages/react-reconciler/src/ReactFiberNewContext.new.js
index 3c03586bb5474..ec66144c060f0 100644
--- a/packages/react-reconciler/src/ReactFiberNewContext.new.js
+++ b/packages/react-reconciler/src/ReactFiberNewContext.new.js
@@ -10,7 +10,7 @@
import type {ReactContext} from 'shared/ReactTypes';
import type {Fiber, ContextDependency} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.new';
@@ -20,12 +20,17 @@ import {
ClassComponent,
DehydratedFragment,
} from './ReactWorkTags';
-import {isSameOrHigherPriority} from './ReactFiberExpirationTime.new';
+import {
+ NoLanes,
+ isSubsetOfLanes,
+ includesSomeLane,
+ combineLanes,
+ pickArbitraryLane,
+} from './ReactFiberLane';
import invariant from 'shared/invariant';
import is from 'shared/objectIs';
import {createUpdate, enqueueUpdate, ForceUpdate} from './ReactUpdateQueue.new';
-import {NoWork} from './ReactFiberExpirationTime.new';
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
@@ -148,37 +153,22 @@ export function calculateChangedBits(
export function scheduleWorkOnParentPath(
parent: Fiber | null,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
) {
- // Update the child expiration time of all the ancestors, including
- // the alternates.
+ // Update the child lanes of all the ancestors, including the alternates.
let node = parent;
while (node !== null) {
const alternate = node.alternate;
- if (
- !isSameOrHigherPriority(
- node.childExpirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- node.childExpirationTime_opaque = renderExpirationTime;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.childExpirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- alternate.childExpirationTime_opaque = renderExpirationTime;
+ if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
+ node.childLanes = combineLanes(node.childLanes, renderLanes);
+ if (alternate !== null) {
+ alternate.childLanes = combineLanes(alternate.childLanes, renderLanes);
}
} else if (
alternate !== null &&
- !isSameOrHigherPriority(
- alternate.childExpirationTime_opaque,
- renderExpirationTime,
- )
+ !isSubsetOfLanes(alternate.childLanes, renderLanes)
) {
- alternate.childExpirationTime_opaque = renderExpirationTime;
+ alternate.childLanes = combineLanes(alternate.childLanes, renderLanes);
} else {
// Neither alternate was updated, which means the rest of the
// ancestor path already has sufficient priority.
@@ -192,7 +182,7 @@ export function propagateContextChange(
workInProgress: Fiber,
context: ReactContext,
changedBits: number,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): void {
let fiber = workInProgress.child;
if (fiber !== null) {
@@ -218,7 +208,11 @@ export function propagateContextChange(
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
- const update = createUpdate(-1, renderExpirationTime, null);
+ const update = createUpdate(
+ -1,
+ pickArbitraryLane(renderLanes),
+ null,
+ );
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
@@ -226,34 +220,15 @@ export function propagateContextChange(
// worth fixing.
enqueueUpdate(fiber, update);
}
-
- if (
- !isSameOrHigherPriority(
- fiber.expirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- fiber.expirationTime_opaque = renderExpirationTime;
- }
+ fiber.lanes = combineLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.expirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- alternate.expirationTime_opaque = renderExpirationTime;
+ if (alternate !== null) {
+ alternate.lanes = combineLanes(alternate.lanes, renderLanes);
}
+ scheduleWorkOnParentPath(fiber.return, renderLanes);
- scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
-
- // Mark the expiration time on the list, too.
- if (
- !isSameOrHigherPriority(list.expirationTime, renderExpirationTime)
- ) {
- list.expirationTime = renderExpirationTime;
- }
+ // Mark the updated lanes on the list, too.
+ list.lanes = combineLanes(list.lanes, renderLanes);
// Since we already found a match, we can stop traversing the
// dependency list.
@@ -276,29 +251,16 @@ export function propagateContextChange(
parentSuspense !== null,
'We just came from a parent so we must have had a parent. This is a bug in React.',
);
- if (
- !isSameOrHigherPriority(
- parentSuspense.expirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- parentSuspense.expirationTime_opaque = renderExpirationTime;
- }
+ parentSuspense.lanes = combineLanes(parentSuspense.lanes, renderLanes);
const alternate = parentSuspense.alternate;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.expirationTime_opaque,
- renderExpirationTime,
- )
- ) {
- alternate.expirationTime_opaque = renderExpirationTime;
+ if (alternate !== null) {
+ alternate.lanes = combineLanes(alternate.lanes, renderLanes);
}
// This is intentionally passing this fiber as the parent
// because we want to schedule this fiber as having work
- // on its children. We'll use the childExpirationTime on
+ // on its children. We'll use the childLanes on
// this fiber to indicate that a context has changed.
- scheduleWorkOnParentPath(parentSuspense, renderExpirationTime);
+ scheduleWorkOnParentPath(parentSuspense, renderLanes);
nextFiber = fiber.sibling;
} else {
// Traverse down.
@@ -334,7 +296,7 @@ export function propagateContextChange(
export function prepareToReadContext(
workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): void {
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;
@@ -344,12 +306,7 @@ export function prepareToReadContext(
if (dependencies !== null) {
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
- if (
- isSameOrHigherPriority(
- dependencies.expirationTime,
- renderExpirationTime,
- )
- ) {
+ if (includesSomeLane(dependencies.lanes, renderLanes)) {
// Context list has a pending update. Mark that this fiber performed work.
markWorkInProgressReceivedUpdate();
}
@@ -411,7 +368,7 @@ export function readContext(
// This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies_new = {
- expirationTime: NoWork,
+ lanes: NoLanes,
firstContext: contextItem,
responders: null,
};
diff --git a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js
index 0808c4c193177..58f2360501774 100644
--- a/packages/react-reconciler/src/ReactFiberOffscreenComponent.js
+++ b/packages/react-reconciler/src/ReactFiberOffscreenComponent.js
@@ -8,7 +8,7 @@
*/
import type {ReactNodeList} from 'shared/ReactTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
export type OffscreenProps = {|
// TODO: Pick an API before exposing the Offscreen type. I've chosen an enum
@@ -24,8 +24,8 @@ export type OffscreenProps = {|
// We use the existence of the state object as an indicator that the component
// is hidden.
export type OffscreenState = {|
- // TODO: This doesn't do anything, yet. It's always NoWork. But eventually it
+ // TODO: This doesn't do anything, yet. It's always NoLanes. But eventually it
// will represent the pending work that must be included in the render in
// order to unhide the component.
- baseTime: ExpirationTimeOpaque,
+ baseLanes: Lanes,
|};
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js
index 0cb5fada23b7c..4b694f7665125 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.new.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js
@@ -19,7 +19,7 @@ import type {
import type {RendererInspectionConfig} from './ReactFiberHostConfig';
import {FundamentalComponent} from './ReactWorkTags';
import type {ReactNodeList} from 'shared/ReactTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lane} from './ReactFiberLane';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import {
@@ -47,7 +47,7 @@ import {createFiberRoot} from './ReactFiberRoot.new';
import {injectInternals, onScheduleRoot} from './ReactFiberDevToolsHook.new';
import {
requestEventTime,
- requestUpdateExpirationTime,
+ requestUpdateLane,
scheduleUpdateOnFiber,
flushRoot,
batchedEventUpdates,
@@ -74,11 +74,12 @@ import {
} from './ReactCurrentFiber';
import {StrictMode} from './ReactTypeOfMode';
import {
- Sync,
- ContinuousHydration,
- UserBlockingUpdateTime,
- isSameOrHigherPriority,
-} from './ReactFiberExpirationTime.new';
+ SyncLane,
+ InputDiscreteHydrationLane,
+ SelectiveHydrationLane,
+ getHighestPriorityPendingLanes,
+ higherPriorityLane,
+} from './ReactFiberLane';
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
import {
scheduleRefresh,
@@ -235,7 +236,7 @@ export function updateContainer(
container: OpaqueRoot,
parentComponent: ?React$Component,
callback: ?Function,
-): ExpirationTimeOpaque {
+): Lane {
if (__DEV__) {
onScheduleRoot(container, element);
}
@@ -249,7 +250,7 @@ export function updateContainer(
}
}
const suspenseConfig = requestCurrentSuspenseConfig();
- const expirationTime = requestUpdateExpirationTime(current, suspenseConfig);
+ const lane = requestUpdateLane(current, suspenseConfig);
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
@@ -275,7 +276,7 @@ export function updateContainer(
}
}
- const update = createUpdate(eventTime, expirationTime, suspenseConfig);
+ const update = createUpdate(eventTime, lane, suspenseConfig);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
@@ -295,9 +296,9 @@ export function updateContainer(
}
enqueueUpdate(current, update);
- scheduleUpdateOnFiber(current, expirationTime);
+ scheduleUpdateOnFiber(current, lane);
- return expirationTime;
+ return lane;
}
export {
@@ -336,40 +337,37 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
const root: FiberRoot = fiber.stateNode;
if (root.hydrate) {
// Flush the first scheduled "update".
- flushRoot(root, root.firstPendingTime_opaque);
+ const lanes = getHighestPriorityPendingLanes(root);
+ flushRoot(root, lanes);
}
break;
case SuspenseComponent:
- flushSync(() =>
- scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque)),
- );
+ flushSync(() => scheduleUpdateOnFiber(fiber, SyncLane));
// If we're still blocked after this, we need to increase
// the priority of any promises resolving within this
// boundary so that they next attempt also has higher pri.
- const retryExpTime = UserBlockingUpdateTime;
- markRetryTimeIfNotHydrated(fiber, retryExpTime);
+ const retryLane = InputDiscreteHydrationLane;
+ markRetryLaneIfNotHydrated(fiber, retryLane);
break;
}
}
-function markRetryTimeImpl(fiber: Fiber, retryTime: ExpirationTimeOpaque) {
+function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
const suspenseState: null | SuspenseState = fiber.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
- if (!isSameOrHigherPriority(suspenseState.retryTime, retryTime)) {
- suspenseState.retryTime = retryTime;
- }
+ suspenseState.retryLane = higherPriorityLane(
+ suspenseState.retryLane,
+ retryLane,
+ );
}
}
// Increases the priority of thennables when they resolve within this boundary.
-function markRetryTimeIfNotHydrated(
- fiber: Fiber,
- retryTime: ExpirationTimeOpaque,
-) {
- markRetryTimeImpl(fiber, retryTime);
+function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
+ markRetryLaneImpl(fiber, retryLane);
const alternate = fiber.alternate;
if (alternate) {
- markRetryTimeImpl(alternate, retryTime);
+ markRetryLaneImpl(alternate, retryLane);
}
}
@@ -381,9 +379,9 @@ export function attemptUserBlockingHydration(fiber: Fiber): void {
// Suspense.
return;
}
- const expTime = UserBlockingUpdateTime;
- scheduleUpdateOnFiber(fiber, expTime);
- markRetryTimeIfNotHydrated(fiber, expTime);
+ const lane = InputDiscreteHydrationLane;
+ scheduleUpdateOnFiber(fiber, lane);
+ markRetryLaneIfNotHydrated(fiber, lane);
}
export function attemptContinuousHydration(fiber: Fiber): void {
@@ -394,11 +392,9 @@ export function attemptContinuousHydration(fiber: Fiber): void {
// Suspense.
return;
}
- scheduleUpdateOnFiber(fiber, (ContinuousHydration: ExpirationTimeOpaque));
- markRetryTimeIfNotHydrated(
- fiber,
- (ContinuousHydration: ExpirationTimeOpaque),
- );
+ const lane = SelectiveHydrationLane;
+ scheduleUpdateOnFiber(fiber, lane);
+ markRetryLaneIfNotHydrated(fiber, lane);
}
export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
@@ -407,9 +403,9 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
// their priority other than synchronously flush it.
return;
}
- const expTime = requestUpdateExpirationTime(fiber, null);
- scheduleUpdateOnFiber(fiber, expTime);
- markRetryTimeIfNotHydrated(fiber, expTime);
+ const lane = requestUpdateLane(fiber, null);
+ scheduleUpdateOnFiber(fiber, lane);
+ markRetryLaneIfNotHydrated(fiber, lane);
}
export {findHostInstance};
@@ -491,7 +487,7 @@ if (__DEV__) {
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
- scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
+ scheduleUpdateOnFiber(fiber, SyncLane);
}
};
@@ -501,11 +497,11 @@ if (__DEV__) {
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}
- scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
+ scheduleUpdateOnFiber(fiber, SyncLane);
};
scheduleUpdate = (fiber: Fiber) => {
- scheduleUpdateOnFiber(fiber, (Sync: ExpirationTimeOpaque));
+ scheduleUpdateOnFiber(fiber, SyncLane);
};
setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js
index ae03056235482..b3fc36d51724a 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.new.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.new.js
@@ -8,25 +8,17 @@
*/
import type {FiberRoot, SuspenseHydrationCallbacks} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
import type {RootTag} from './ReactRootTags';
import {noTimeout} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.new';
-import {
- NoWork,
- isSameOrHigherPriority,
- isSameExpirationTime,
- bumpPriorityHigher,
- bumpPriorityLower,
-} from './ReactFiberExpirationTime.new';
+import {NoLanes} from './ReactFiberLane';
import {
enableSchedulerTracing,
enableSuspenseCallback,
} from 'shared/ReactFeatureFlags';
import {unstable_getThreadID} from 'scheduler/tracing';
import {initializeUpdateQueue} from './ReactUpdateQueue.new';
-import {clearPendingUpdates as clearPendingMutableSourceUpdates} from './ReactMutableSource.new';
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
@@ -35,23 +27,22 @@ function FiberRootNode(containerInfo, tag, hydrate) {
this.current = null;
this.pingCache = null;
this.finishedWork = null;
- this.finishedExpirationTime_opaque = NoWork;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
- this.callbackId = NoWork;
+ this.callbackId = NoLanes;
this.callbackIsSync = false;
this.expiresAt = -1;
- this.firstPendingTime_opaque = NoWork;
- this.lastPendingTime_opaque = NoWork;
- this.firstSuspendedTime_opaque = NoWork;
- this.lastSuspendedTime_opaque = NoWork;
- this.nextKnownPendingLevel_opaque = NoWork;
- this.lastPingedTime_opaque = NoWork;
- this.lastExpiredTime_opaque = NoWork;
- this.mutableSourceLastPendingUpdateTime_opaque = NoWork;
+
+ this.pendingLanes = NoLanes;
+ this.suspendedLanes = NoLanes;
+ this.pingedLanes = NoLanes;
+ this.expiredLanes = NoLanes;
+ this.mutableReadLanes = NoLanes;
+
+ this.finishedLanes = NoLanes;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
@@ -84,154 +75,3 @@ export function createFiberRoot(
return root;
}
-
-export function isRootSuspendedAtTime(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): boolean {
- const firstSuspendedTime = root.firstSuspendedTime_opaque;
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
- return (
- !isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque)) &&
- isSameOrHigherPriority(firstSuspendedTime, expirationTime) &&
- isSameOrHigherPriority(expirationTime, lastSuspendedTime)
- );
-}
-
-export function markRootSuspendedAtTime(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): void {
- const firstSuspendedTime = root.firstSuspendedTime_opaque;
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
- if (!isSameOrHigherPriority(firstSuspendedTime, expirationTime)) {
- root.firstSuspendedTime_opaque = expirationTime;
- }
- if (
- !isSameOrHigherPriority(expirationTime, lastSuspendedTime) ||
- isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque))
- ) {
- root.lastSuspendedTime_opaque = expirationTime;
- }
-
- if (isSameOrHigherPriority(root.lastPingedTime_opaque, expirationTime)) {
- root.lastPingedTime_opaque = NoWork;
- }
-
- if (isSameOrHigherPriority(root.lastExpiredTime_opaque, expirationTime)) {
- root.lastExpiredTime_opaque = NoWork;
- }
-}
-
-export function markRootUpdatedAtTime(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): void {
- // Update the range of pending times
- const firstPendingTime = root.firstPendingTime_opaque;
- if (!isSameOrHigherPriority(firstPendingTime, expirationTime)) {
- root.firstPendingTime_opaque = expirationTime;
- }
- const lastPendingTime = root.lastPendingTime_opaque;
- if (
- isSameExpirationTime(lastPendingTime, (NoWork: ExpirationTimeOpaque)) ||
- !isSameOrHigherPriority(expirationTime, lastPendingTime)
- ) {
- root.lastPendingTime_opaque = expirationTime;
- }
-
- // Update the range of suspended times. Treat everything lower priority or
- // equal to this update as unsuspended.
- const firstSuspendedTime = root.firstSuspendedTime_opaque;
- if (
- !isSameExpirationTime(firstSuspendedTime, (NoWork: ExpirationTimeOpaque))
- ) {
- if (isSameOrHigherPriority(expirationTime, firstSuspendedTime)) {
- // The entire suspended range is now unsuspended.
- root.firstSuspendedTime_opaque = root.lastSuspendedTime_opaque = root.nextKnownPendingLevel_opaque = NoWork;
- } else if (
- isSameOrHigherPriority(expirationTime, root.lastSuspendedTime_opaque)
- ) {
- root.lastSuspendedTime_opaque = bumpPriorityHigher(expirationTime);
- }
-
- // This is a pending level. Check if it's higher priority than the next
- // known pending level.
- if (
- !isSameOrHigherPriority(root.nextKnownPendingLevel_opaque, expirationTime)
- ) {
- root.nextKnownPendingLevel_opaque = expirationTime;
- }
- }
-}
-
-export function markRootFinishedAtTime(
- root: FiberRoot,
- finishedExpirationTime: ExpirationTimeOpaque,
- remainingExpirationTime: ExpirationTimeOpaque,
-): void {
- // Update the range of pending times
- root.firstPendingTime_opaque = remainingExpirationTime;
- if (
- !isSameOrHigherPriority(
- remainingExpirationTime,
- root.lastPendingTime_opaque,
- )
- ) {
- // This usually means we've finished all the work, but it can also happen
- // when something gets downprioritized during render, like a hidden tree.
- root.lastPendingTime_opaque = remainingExpirationTime;
- }
-
- // Update the range of suspended times. Treat everything higher priority or
- // equal to this update as unsuspended.
- if (
- isSameOrHigherPriority(
- root.lastSuspendedTime_opaque,
- finishedExpirationTime,
- )
- ) {
- // The entire suspended range is now unsuspended.
- root.firstSuspendedTime_opaque = root.lastSuspendedTime_opaque = root.nextKnownPendingLevel_opaque = NoWork;
- } else if (
- isSameOrHigherPriority(
- root.firstSuspendedTime_opaque,
- finishedExpirationTime,
- )
- ) {
- // Part of the suspended range is now unsuspended. Narrow the range to
- // include everything between the unsuspended time (non-inclusive) and the
- // last suspended time.
- root.firstSuspendedTime_opaque = bumpPriorityLower(finishedExpirationTime);
- }
-
- if (
- isSameOrHigherPriority(root.lastPingedTime_opaque, finishedExpirationTime)
- ) {
- // Clear the pinged time
- root.lastPingedTime_opaque = NoWork;
- }
-
- if (
- isSameOrHigherPriority(root.lastExpiredTime_opaque, finishedExpirationTime)
- ) {
- // Clear the expired time
- root.lastExpiredTime_opaque = NoWork;
- }
-
- // Clear any pending updates that were just processed.
- clearPendingMutableSourceUpdates(root, finishedExpirationTime);
-}
-
-export function markRootExpiredAtTime(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): void {
- const lastExpiredTime = root.lastExpiredTime_opaque;
- if (
- isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque)) ||
- !isSameOrHigherPriority(expirationTime, lastExpiredTime)
- ) {
- root.lastExpiredTime_opaque = expirationTime;
- }
-}
diff --git a/packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js b/packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
index a45f1710138fb..e151ab0be0088 100644
--- a/packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
+++ b/packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
@@ -9,7 +9,7 @@
import type {Fiber} from './ReactInternalTypes';
import type {SuspenseInstance} from './ReactFiberHostConfig';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lane} from './ReactFiberLane';
import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
import {NoEffect, DidCapture} from './ReactSideEffectTags';
import {
@@ -29,11 +29,10 @@ export type SuspenseState = {|
// here to indicate that it is dehydrated (flag) and for quick access
// to check things like isSuspenseInstancePending.
dehydrated: null | SuspenseInstance,
- // Represents the earliest expiration time we should attempt to hydrate
- // a dehydrated boundary at.
- // Never is the default for dehydrated boundaries.
- // NoWork is the default for normal boundaries, which turns into "normal" pri.
- retryTime: ExpirationTimeOpaque,
+ // Represents the lane we should attempt to hydrate a dehydrated boundary at.
+ // OffscreenLane is the default for dehydrated boundaries.
+ // NoLane is the default for normal boundaries, which turns into "normal" pri.
+ retryLane: Lane,
|};
export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
diff --git a/packages/react-reconciler/src/ReactFiberThrow.new.js b/packages/react-reconciler/src/ReactFiberThrow.new.js
index a63deaaef1133..f08655f8312fc 100644
--- a/packages/react-reconciler/src/ReactFiberThrow.new.js
+++ b/packages/react-reconciler/src/ReactFiberThrow.new.js
@@ -9,7 +9,7 @@
import type {Fiber} from './ReactInternalTypes';
import type {FiberRoot} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lane, Lanes} from './ReactFiberLane';
import type {CapturedValue} from './ReactCapturedValue';
import type {Update} from './ReactUpdateQueue.new';
import type {Wakeable} from 'shared/ReactTypes';
@@ -54,16 +54,21 @@ import {
} from './ReactFiberWorkLoop.new';
import {logCapturedError} from './ReactFiberErrorLogger';
-import {Sync, isSameExpirationTime} from './ReactFiberExpirationTime.new';
+import {
+ SyncLane,
+ includesSomeLane,
+ combineLanes,
+ pickArbitraryLane,
+} from './ReactFiberLane';
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane,
): Update {
- const update = createUpdate(-1, expirationTime, null);
+ const update = createUpdate(-1, lane, null);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
@@ -80,9 +85,9 @@ function createRootErrorUpdate(
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane,
): Update {
- const update = createUpdate(-1, expirationTime, null);
+ const update = createUpdate(-1, lane, null);
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
@@ -120,12 +125,7 @@ function createClassErrorUpdate(
// If componentDidCatch is the only error boundary method defined,
// then it needs to call setState to recover from errors.
// If no state update is scheduled then the boundary will swallow the error.
- if (
- !isSameExpirationTime(
- fiber.expirationTime_opaque,
- (Sync: ExpirationTimeOpaque),
- )
- ) {
+ if (!includesSomeLane(fiber.lanes, (SyncLane: Lane))) {
console.error(
'%s: Error boundaries should implement getDerivedStateFromError(). ' +
'In that method, return a state update to display an error message or fallback UI.',
@@ -143,14 +143,10 @@ function createClassErrorUpdate(
return update;
}
-function attachPingListener(
- root: FiberRoot,
- renderExpirationTime: ExpirationTimeOpaque,
- wakeable: Wakeable,
-) {
- // Attach a listener to the promise to "ping" the root and retry. But
- // only if one does not already exist for the current render expiration
- // time (which acts like a "thread ID" here).
+function attachPingListener(root: FiberRoot, wakeable: Wakeable, lanes: Lanes) {
+ // Attach a listener to the promise to "ping" the root and retry. But only if
+ // one does not already exist for the lanes we're currently rendering (which
+ // acts like a "thread ID" here).
let pingCache = root.pingCache;
let threadIDs;
if (pingCache === null) {
@@ -164,15 +160,10 @@ function attachPingListener(
pingCache.set(wakeable, threadIDs);
}
}
- if (!threadIDs.has(renderExpirationTime)) {
+ if (!threadIDs.has(lanes)) {
// Memoize using the thread ID to prevent redundant listeners.
- threadIDs.add(renderExpirationTime);
- const ping = pingSuspendedRoot.bind(
- null,
- root,
- wakeable,
- renderExpirationTime,
- );
+ threadIDs.add(lanes);
+ const ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
wakeable.then(ping, ping);
}
}
@@ -182,7 +173,7 @@ function throwException(
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed,
- renderExpirationTime: ExpirationTimeOpaque,
+ rootRenderLanes: Lanes,
) {
// The source fiber did not complete.
sourceFiber.effectTag |= Incomplete;
@@ -204,7 +195,7 @@ function throwException(
if (currentSource) {
sourceFiber.updateQueue = currentSource.updateQueue;
sourceFiber.memoizedState = currentSource.memoizedState;
- sourceFiber.expirationTime_opaque = currentSource.expirationTime_opaque;
+ sourceFiber.lanes = currentSource.lanes;
} else {
sourceFiber.updateQueue = null;
sourceFiber.memoizedState = null;
@@ -263,11 +254,7 @@ function throwException(
// When we try rendering again, we should not reuse the current fiber,
// since it's known to be in an inconsistent state. Use a force update to
// prevent a bail out.
- const update = createUpdate(
- -1,
- (Sync: ExpirationTimeOpaque),
- null,
- );
+ const update = createUpdate(-1, SyncLane, null);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update);
}
@@ -275,7 +262,7 @@ function throwException(
// The source fiber did not complete. Mark it with Sync priority to
// indicate that it still has pending work.
- sourceFiber.expirationTime_opaque = Sync;
+ sourceFiber.lanes = combineLanes(sourceFiber.lanes, SyncLane);
// Exit without suspending.
return;
@@ -323,10 +310,10 @@ function throwException(
// We want to ensure that a "busy" state doesn't get force committed. We want to
// ensure that new initial loading states can commit as soon as possible.
- attachPingListener(root, renderExpirationTime, wakeable);
+ attachPingListener(root, wakeable, rootRenderLanes);
workInProgress.effectTag |= ShouldCapture;
- workInProgress.expirationTime_opaque = renderExpirationTime;
+ workInProgress.lanes = rootRenderLanes;
return;
}
@@ -349,6 +336,7 @@ function throwException(
// over and traverse parent path again, this time treating the exception
// as an error.
renderDidError();
+
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
do {
@@ -356,12 +344,9 @@ function throwException(
case HostRoot: {
const errorInfo = value;
workInProgress.effectTag |= ShouldCapture;
- workInProgress.expirationTime_opaque = renderExpirationTime;
- const update = createRootErrorUpdate(
- workInProgress,
- errorInfo,
- renderExpirationTime,
- );
+ const lane = pickArbitraryLane(rootRenderLanes);
+ workInProgress.lanes = combineLanes(workInProgress.lanes, lane);
+ const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
enqueueCapturedUpdate(workInProgress, update);
return;
}
@@ -378,12 +363,13 @@ function throwException(
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.effectTag |= ShouldCapture;
- workInProgress.expirationTime_opaque = renderExpirationTime;
+ const lane = pickArbitraryLane(rootRenderLanes);
+ workInProgress.lanes = combineLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
- renderExpirationTime,
+ lane,
);
enqueueCapturedUpdate(workInProgress, update);
return;
diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.new.js b/packages/react-reconciler/src/ReactFiberUnwindWork.new.js
index 64bed32254618..735888f3215d2 100644
--- a/packages/react-reconciler/src/ReactFiberUnwindWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberUnwindWork.new.js
@@ -8,7 +8,7 @@
*/
import type {Fiber} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes} from './ReactFiberLane';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
@@ -35,14 +35,11 @@ import {
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext.new';
import {popProvider} from './ReactFiberNewContext.new';
-import {popRenderExpirationTime} from './ReactFiberWorkLoop.new';
+import {popRenderLanes} from './ReactFiberWorkLoop.new';
import invariant from 'shared/invariant';
-function unwindWork(
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTimeOpaque,
-) {
+function unwindWork(workInProgress: Fiber, renderLanes: Lanes) {
switch (workInProgress.tag) {
case ClassComponent: {
const Component = workInProgress.type;
@@ -110,7 +107,7 @@ function unwindWork(
return null;
case OffscreenComponent:
case LegacyHiddenComponent:
- popRenderExpirationTime(workInProgress);
+ popRenderLanes(workInProgress);
return null;
default:
return null;
@@ -150,7 +147,7 @@ function unwindInterruptedWork(interruptedWork: Fiber) {
break;
case OffscreenComponent:
case LegacyHiddenComponent:
- popRenderExpirationTime(interruptedWork);
+ popRenderLanes(interruptedWork);
break;
default:
break;
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index 544d4f72b14a4..1e4c9fdaf4010 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -9,7 +9,7 @@
import type {Thenable, Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes, Lane} from './ReactFiberLane';
import type {ReactPriorityLevel} from './ReactInternalTypes';
import type {Interaction} from 'scheduler/src/Tracing';
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
@@ -43,8 +43,6 @@ import {
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
- LowPriority as LowSchedulerPriority,
- IdlePriority as IdleSchedulerPriority,
flushSyncCallbackQueue,
scheduleSyncCallback,
} from './SchedulerWithReactIntegration.new';
@@ -69,13 +67,6 @@ import {
createWorkInProgress,
assignFiberPropertiesInDEV,
} from './ReactFiber.new';
-import {
- isRootSuspendedAtTime,
- markRootSuspendedAtTime,
- markRootFinishedAtTime,
- markRootUpdatedAtTime,
- markRootExpiredAtTime,
-} from './ReactFiberRoot.new';
import {
NoMode,
StrictMode,
@@ -93,6 +84,8 @@ import {
MemoComponent,
SimpleMemoComponent,
Block,
+ OffscreenComponent,
+ LegacyHiddenComponent,
} from './ReactWorkTags';
import {LegacyRoot} from './ReactRootTags';
import {
@@ -113,33 +106,35 @@ import {
Hydrating,
HydratingAndUpdate,
} from './ReactSideEffectTags';
-import {
- NoWork,
- Sync,
- UserBlockingUpdateTime,
- DefaultUpdateTime,
- Never,
- inferPriorityFromExpirationTime,
- Batched,
- Idle,
- ContinuousHydration,
- ShortTransition,
- LongTransition,
- isSameOrHigherPriority,
- isSameExpirationTime,
- bumpPriorityLower,
-} from './ReactFiberExpirationTime.new';
import {
SyncLanePriority,
- SyncBatchedLanePriority,
InputDiscreteLanePriority,
- InputContinuousLanePriority,
- DefaultLanePriority,
TransitionShortLanePriority,
TransitionLongLanePriority,
- HydrationContinuousLanePriority,
- IdleLanePriority,
- OffscreenLanePriority,
+ NoLanes,
+ NoLane,
+ SyncLane,
+ SyncBatchedLane,
+ OffscreenLane,
+ findUpdateLane,
+ findTransitionLane,
+ includesSomeLane,
+ isSubsetOfLanes,
+ combineLanes,
+ removeLanes,
+ hasDiscreteLanes,
+ hasUpdatePriority,
+ getNextLanes,
+ returnNextLanesPriority,
+ getLanesToRetrySynchronouslyOnError,
+ markRootUpdated,
+ markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
+ markRootPinged,
+ markRootExpired,
+ markDiscreteUpdatesExpired,
+ markRootFinished,
+ schedulerPriorityToLanePriority,
+ lanePriorityToSchedulerPriority,
} from './ReactFiberLane';
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
import {completeWork} from './ReactFiberCompleteWork.new';
@@ -235,33 +230,43 @@ let executionContext: ExecutionContext = NoContext;
let workInProgressRoot: FiberRoot | null = null;
// The fiber we're working on
let workInProgress: Fiber | null = null;
-// The expiration time we're rendering
-let renderExpirationTime: ExpirationTimeOpaque = NoWork;
-
-// Stack that allows components to channge renderExpirationTime for its subtree
-const renderExpirationTimeCursor: StackCursor = createCursor(
- NoWork,
-);
+// The lanes we're rendering
+let workInProgressRootRenderLanes: Lanes = NoLanes;
+
+// Stack that allows components to change the render lanes for its subtree
+// This is a superset of the lanes we started working on at the root. The only
+// case where it's different from `workInProgressRootRenderLanes` is when we
+// enter a subtree that is hidden and needs to be unhidden: Suspense and
+// Offscreen component.
+//
+// Most things in the work loop should deal with workInProgressRootRenderLanes.
+// Most things in begin/complete phases should deal with subtreeRenderLanes.
+let subtreeRenderLanes: Lanes = NoLanes;
+const subtreeRenderLanesCursor: StackCursor = createCursor(NoLanes);
// Whether to root completed, errored, suspended, etc.
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
// A fatal error, if one is thrown
let workInProgressRootFatalError: mixed = null;
// Most recent event time among processed updates during this render.
-// This is conceptually a time stamp but expressed in terms of an ExpirationTimeOpaque
-// because we deal mostly with expiration times in the hot path, so this avoids
-// the conversion happening in the hot path.
let workInProgressRootLatestProcessedEventTime: number = -1;
let workInProgressRootLatestSuspenseTimeout: number = -1;
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
+// "Included" lanes refer to lanes that were worked on during this render. It's
+// slightly different than `renderLanes` because `renderLanes` can change as you
+// enter and exit an Offscreen tree. This value is the combination of all render
+// lanes for the entire render phase.
+let workInProgressRootIncludedLanes: Lanes = NoLanes;
// The work left over by components that were visited during this render. Only
// includes unprocessed updates, not work in bailed out children.
-let workInProgressRootNextUnprocessedUpdateTime: ExpirationTimeOpaque = NoWork;
+let workInProgressRootSkippedLanes: Lanes = NoLanes;
+// Lanes that were updated (in an interleaved event) during this render.
+let workInProgressRootUpdatedLanes: Lanes = NoLanes;
+// Lanes that were pinged (in an interleaved event) during this render.
+let workInProgressRootPingedLanes: Lanes = NoLanes;
+
+let mostRecentlyUpdatedRoot: FiberRoot | null = null;
-// If we're pinged while rendering we don't always restart immediately.
-// This flag determines if it might be worthwhile to restart if an opportunity
-// happens latere.
-let workInProgressRootHasPendingPing: boolean = false;
// The most recent time we committed a fallback. This lets us ensure a train
// model where we don't commit new loading states in too quick succession.
let globalMostRecentFallbackTime: number = 0;
@@ -276,15 +281,12 @@ let legacyErrorBoundariesThatAlreadyFailed: Set | null = null;
let rootDoesHavePassiveEffects: boolean = false;
let rootWithPendingPassiveEffects: FiberRoot | null = null;
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
-let pendingPassiveEffectsExpirationTime: ExpirationTimeOpaque = NoWork;
+let pendingPassiveEffectsLanes: Lanes = NoLanes;
let pendingPassiveHookEffectsMount: Array = [];
let pendingPassiveHookEffectsUnmount: Array = [];
let pendingPassiveProfilerEffects: Array = [];
-let rootsWithPendingDiscreteUpdates: Map<
- FiberRoot,
- ExpirationTimeOpaque,
-> | null = null;
+let rootsWithPendingDiscreteUpdates: Set | null = null;
// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT = 50;
@@ -294,16 +296,19 @@ let rootWithNestedUpdates: FiberRoot | null = null;
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
let nestedPassiveUpdateCount: number = 0;
-// Marks the need to reschedule pending interactions at these expiration times
+// Marks the need to reschedule pending interactions at these lanes
// during the commit phase. This enables them to be traced across components
// that spawn new work during render. E.g. hidden boundaries, suspended SSR
// hydration or SuspenseList.
-let spawnedWorkDuringRender: null | Array = null;
+// TODO: Can use a bitmask instead of an array
+let spawnedWorkDuringRender: null | Array = null;
// If two updates are scheduled within the same event, we should treat their
// event times as simultaneous, even if the actual clock time has advanced
// between the first and second call.
let currentEventTime: number = -1;
+let currentEventWipLanes: Lanes = NoLanes;
+let currentEventPendingLanes: Lanes = NoLanes;
// Dev only flag that tracks if passive effects are currently being flushed.
// We warn about state updates for unmounted components differently in this case.
@@ -335,121 +340,86 @@ export function getCurrentTime() {
return now();
}
-export function requestUpdateExpirationTime(
+export function requestUpdateLane(
fiber: Fiber,
suspenseConfig: SuspenseConfig | null,
-): ExpirationTimeOpaque {
+): Lane {
// Special cases
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
- return Sync;
+ return (SyncLane: Lane);
} else if ((mode & ConcurrentMode) === NoMode) {
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
- ? (Sync: ExpirationTimeOpaque)
- : Batched;
- } else if ((executionContext & RenderContext) !== NoContext) {
- // Use whatever time we're already rendering
- // TODO: Treat render phase updates as if they came from an
- // interleaved event.
- return renderExpirationTime;
- }
-
- let updateLanePriority;
+ ? (SyncLane: Lane)
+ : (SyncBatchedLane: Lane);
+ }
+
+ // The algorithm for assigning an update to a lane should be stable for all
+ // updates at the same priority within the same event. To do this, the inputs
+ // to the algorithm must be the same. For example, we use the `renderLanes`
+ // to avoid choosing a lane that is already in the middle of rendering.
+ //
+ // However, the "included" lanes could be mutated in between updates in the
+ // same event, like if you perform an update inside `flushSync`. Or any other
+ // codepath that might call `prepareFreshStack`.
+ //
+ // The trick we use is to cache the first of each of these inputs within an
+ // event. Then reset the cached values once we can be sure the event is over.
+ // Our heuristic for that is whenever we enter a concurrent work loop.
+ //
+ // We'll do the same for `currentEventPendingLanes` below.
+ if (currentEventWipLanes === NoLanes) {
+ currentEventWipLanes = workInProgressRootIncludedLanes;
+ }
+
+ let lane;
if (suspenseConfig !== null) {
- // If there's a SuspenseConfig, choose an expiration time that's lower
- // priority than a normal concurrent update (regardless of the current
- // Scheduler priority.) Timeouts larger than 10 seconds move one level
- // lower than that.
+ // Use the size of the timeout as a heuristic to prioritize shorter
+ // transitions over longer ones.
// TODO: This will coerce numbers larger than 31 bits to 0.
const timeoutMs = suspenseConfig.timeoutMs;
- updateLanePriority =
+ const transitionLanePriority =
timeoutMs === undefined || (timeoutMs | 0) < 10000
? TransitionShortLanePriority
: TransitionLongLanePriority;
+
+ if (currentEventPendingLanes !== NoLanes) {
+ currentEventPendingLanes =
+ mostRecentlyUpdatedRoot !== null
+ ? mostRecentlyUpdatedRoot.pendingLanes
+ : NoLanes;
+ }
+
+ lane = findTransitionLane(
+ transitionLanePriority,
+ currentEventWipLanes,
+ currentEventPendingLanes,
+ );
} else {
// TODO: If we're not inside `runWithPriority`, this returns the priority
// of the currently running task. That's probably not what we want.
- const priorityLevel = getCurrentPriorityLevel();
- switch (priorityLevel) {
- case ImmediateSchedulerPriority:
- updateLanePriority = SyncLanePriority;
- break;
- case UserBlockingSchedulerPriority:
- updateLanePriority = InputContinuousLanePriority;
- break;
- case NormalSchedulerPriority:
- case LowSchedulerPriority:
- // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
- updateLanePriority = DefaultLanePriority;
- break;
- case IdleSchedulerPriority:
- updateLanePriority = IdleLanePriority;
- break;
- default:
- invariant(false, 'Expected a valid priority level');
- }
- }
-
- // TODO: In the new system, what we'll do here is claim one of the bits of
- // the root's `pendingLanes` field, based on its priority. We'll combine this
- // function with `scheduleUpdateOnFiber` and `markRootUpdatedAtTime`.
- let expirationTime;
- switch (updateLanePriority) {
- case SyncLanePriority:
- return Sync;
- case SyncBatchedLanePriority:
- return Batched;
- case InputDiscreteLanePriority:
- case InputContinuousLanePriority:
- expirationTime = UserBlockingUpdateTime;
- break;
- case DefaultLanePriority:
- expirationTime = DefaultUpdateTime;
- break;
- case TransitionShortLanePriority:
- expirationTime = ShortTransition;
- break;
- case TransitionLongLanePriority:
- expirationTime = LongTransition;
- break;
- case HydrationContinuousLanePriority:
- expirationTime = ContinuousHydration;
- break;
- case IdleLanePriority:
- expirationTime = Idle;
- break;
- case OffscreenLanePriority:
- expirationTime = Never;
- break;
- default:
- invariant(false, 'Expected a valid priority level');
- }
+ const schedulerPriority = getCurrentPriorityLevel();
- // If we're in the middle of rendering a tree, do not update at the same
- // expiration time that is already rendering.
- // TODO: We shouldn't have to do this if the update is on a different root.
- // TODO: In the new system, we'll find a different bit that's not the one
- // we're currently rendering.
- if (
- workInProgressRoot !== null &&
- isSameExpirationTime(expirationTime, renderExpirationTime)
- ) {
- // This is a trick to move this update into a separate batch
- // TODO: This probably causes problems with ContinuousHydration and Idle
- expirationTime = bumpPriorityLower(expirationTime);
+ if (
+ // TODO: Temporary. We're removing the concept of discrete updates.
+ (executionContext & DiscreteEventContext) !== NoContext &&
+ schedulerPriority === UserBlockingSchedulerPriority
+ ) {
+ lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
+ } else {
+ const lanePriority = schedulerPriorityToLanePriority(schedulerPriority);
+ lane = findUpdateLane(lanePriority, currentEventWipLanes);
+ }
}
- return expirationTime;
+ return lane;
}
-export function scheduleUpdateOnFiber(
- fiber: Fiber,
- expirationTime: ExpirationTimeOpaque,
-) {
+export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane) {
checkForNestedUpdates();
warnAboutRenderPhaseUpdatesInDEV(fiber);
- const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
+ const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return null;
@@ -459,7 +429,7 @@ export function scheduleUpdateOnFiber(
// priority as an argument to that function and this one.
const priorityLevel = getCurrentPriorityLevel();
- if (isSameExpirationTime(expirationTime, (Sync: ExpirationTimeOpaque))) {
+ if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
@@ -467,7 +437,7 @@ export function scheduleUpdateOnFiber(
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register pending interactions on the root to avoid losing traced interaction data.
- schedulePendingInteractions(root, expirationTime);
+ schedulePendingInteractions(root, lane);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
@@ -475,7 +445,7 @@ export function scheduleUpdateOnFiber(
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, expirationTime);
+ schedulePendingInteractions(root, lane);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
@@ -497,38 +467,37 @@ export function scheduleUpdateOnFiber(
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
- rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
+ rootsWithPendingDiscreteUpdates = new Set([root]);
} else {
- const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
- if (
- lastDiscreteTime === undefined ||
- !isSameOrHigherPriority(expirationTime, lastDiscreteTime)
- ) {
- rootsWithPendingDiscreteUpdates.set(root, expirationTime);
- }
+ rootsWithPendingDiscreteUpdates.add(root);
}
}
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, expirationTime);
+ schedulePendingInteractions(root, lane);
}
+
+ // We use this when assigning a lane for a transition inside
+ // `requestUpdateLane`. We assume it's the same as the root being updated,
+ // since in the common case of a single root app it probably is. If it's not
+ // the same root, then it's not a huge deal, we just might batch more stuff
+ // together more than necessary.
+ mostRecentlyUpdatedRoot = root;
}
// This is split into a separate function so we can mark a fiber with pending
// work without treating it as a typical update that originates from an event;
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
// on a fiber.
-function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
- // Update the source fiber's expiration time
- if (!isSameOrHigherPriority(fiber.expirationTime_opaque, expirationTime)) {
- fiber.expirationTime_opaque = expirationTime;
- }
+function markUpdateLaneFromFiberToRoot(
+ fiber: Fiber,
+ lane: Lane,
+): FiberRoot | null {
+ // Update the source fiber's lanes
+ fiber.lanes = combineLanes(fiber.lanes, lane);
let alternate = fiber.alternate;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(alternate.expirationTime_opaque, expirationTime)
- ) {
- alternate.expirationTime_opaque = expirationTime;
+ if (alternate !== null) {
+ alternate.lanes = combineLanes(alternate.lanes, lane);
}
// Walk the parent path to the root and update the child expiration time.
let node = fiber.return;
@@ -538,27 +507,9 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
} else {
while (node !== null) {
alternate = node.alternate;
- if (
- !isSameOrHigherPriority(node.childExpirationTime_opaque, expirationTime)
- ) {
- node.childExpirationTime_opaque = expirationTime;
- if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.childExpirationTime_opaque,
- expirationTime,
- )
- ) {
- alternate.childExpirationTime_opaque = expirationTime;
- }
- } else if (
- alternate !== null &&
- !isSameOrHigherPriority(
- alternate.childExpirationTime_opaque,
- expirationTime,
- )
- ) {
- alternate.childExpirationTime_opaque = expirationTime;
+ node.childLanes = combineLanes(node.childLanes, lane);
+ if (alternate !== null) {
+ alternate.childLanes = combineLanes(alternate.childLanes, lane);
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
@@ -569,76 +520,30 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
}
if (root !== null) {
+ // Mark that the root has a pending update.
+ markRootUpdated(root, lane);
if (workInProgressRoot === root) {
// Received an update to a tree that's in the middle of rendering. Mark
- // that's unprocessed work on this root.
- markUnprocessedUpdateTime(expirationTime);
-
+ // that there is unprocessed work on this root.
+ workInProgressRootUpdatedLanes = combineLanes(
+ workInProgressRootUpdatedLanes,
+ lane,
+ );
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
- // TODO: This happens to work when receiving an update during the render
- // phase, because of the trick inside requestUpdateExpirationTime to
- // subtract 1 from `renderExpirationTime` to move it into a
- // separate bucket. But we should probably model it with an exception,
- // using the same mechanism we use to force hydration of a subtree.
- // TODO: This does not account for low pri updates that were already
- // scheduled before the root started rendering. Need to track the next
- // pending expiration time (perhaps by backtracking the return path) and
- // then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
- markRootSuspendedAtTime(root, renderExpirationTime);
+ // TODO: Make sure this doesn't override pings that happen while we've
+ // already started rendering.
+ markRootSuspended(root, workInProgressRootRenderLanes);
}
}
- // Mark that the root has a pending update.
- markRootUpdatedAtTime(root, expirationTime);
}
return root;
}
-function getNextRootExpirationTimeToWorkOn(
- root: FiberRoot,
-): ExpirationTimeOpaque {
- // Determines the next expiration time that the root should render, taking
- // into account levels that may be suspended, or levels that may have
- // received a ping.
-
- const lastExpiredTime = root.lastExpiredTime_opaque;
- if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
- return lastExpiredTime;
- }
-
- // "Pending" refers to any update that hasn't committed yet, including if it
- // suspended. The "suspended" range is therefore a subset.
- const firstPendingTime = root.firstPendingTime_opaque;
- if (!isRootSuspendedAtTime(root, firstPendingTime)) {
- // The highest priority pending time is not suspended. Let's work on that.
- return firstPendingTime;
- }
-
- // If the first pending time is suspended, check if there's a lower priority
- // pending level that we know about. Or check if we received a ping. Work
- // on whichever is higher priority.
- const lastPingedTime = root.lastPingedTime_opaque;
- const nextKnownPendingLevel = root.nextKnownPendingLevel_opaque;
- const nextLevel = !isSameOrHigherPriority(
- nextKnownPendingLevel,
- lastPingedTime,
- )
- ? lastPingedTime
- : nextKnownPendingLevel;
- if (
- isSameOrHigherPriority((Idle: ExpirationTimeOpaque), nextLevel) &&
- !isSameExpirationTime(firstPendingTime, nextLevel)
- ) {
- // Don't work on Idle/Never priority unless everything else is committed.
- return NoWork;
- }
- return nextLevel;
-}
-
// Use this function to schedule a task for a root. There's only one task per
// root; if a task was already scheduled, we'll check to make sure the
// expiration time of the existing task is the same as the expiration time of
@@ -647,30 +552,31 @@ function getNextRootExpirationTimeToWorkOn(
function ensureRootIsScheduled(root: FiberRoot) {
const existingCallbackNode = root.callbackNode;
- const newCallbackId = getNextRootExpirationTimeToWorkOn(root);
- if (newCallbackId === (NoWork: ExpirationTimeOpaque)) {
+ const newCallbackId = getNextLanes(
+ root,
+ root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
+ );
+ // This returns the priority level computed during the `getNextLanes` call.
+ const newCallbackPriorityLevel = returnNextLanesPriority();
+
+ if (newCallbackId === NoLanes) {
// Special case: There's nothing to work on.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
root.expiresAt = -1;
root.callbackNode = null;
root.callbackIsSync = false;
- root.callbackId = NoWork;
+ root.callbackId = NoLanes;
}
return;
}
- const newTaskIsSync =
- newCallbackId === (Sync: ExpirationTimeOpaque) ||
- !isSameExpirationTime(
- root.lastExpiredTime_opaque,
- (NoWork: ExpirationTimeOpaque),
- );
+ const newTaskIsSync = newCallbackPriorityLevel === SyncLanePriority;
// Check if there's an existing task. We may be able to reuse it.
const existingTaskId = root.callbackId;
const existingCallbackIsSync = root.callbackIsSync;
- if (existingTaskId !== (NoWork: ExpirationTimeOpaque)) {
+ if (existingTaskId !== NoLanes) {
if (newCallbackId === existingTaskId) {
// This task is already scheduled. Let's check its priority.
if (
@@ -689,48 +595,19 @@ function ensureRootIsScheduled(root: FiberRoot) {
// Schedule a new callback.
let newCallbackNode;
if (newTaskIsSync) {
- // Special case: Sync React callbacks are scheduled on a special internal queue
+ // Special case: Sync React callbacks are scheduled on a special
+ // internal queue
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else {
- // TODO: Use LanePriority instead of SchedulerPriority
- const priorityLevel = inferPriorityFromExpirationTime(newCallbackId);
- if (
- priorityLevel === NormalSchedulerPriority ||
- priorityLevel === UserBlockingSchedulerPriority
- ) {
- const existingExpirationTime = root.expiresAt;
- const currentTimeMs = now();
-
- // Compute an expiration time based on the priority level.
- const expiration =
- priorityLevel === UserBlockingSchedulerPriority ? 250 : 5000;
-
- let msUntilExpiration;
- if (existingExpirationTime === -1) {
- // This is the first concurrent update on the root. Use the expiration
- // time we just computed.
- msUntilExpiration = expiration;
- root.expiresAt = msUntilExpiration + currentTimeMs;
- } else {
- // There's already an expiration time. Use the smaller of the current
- // expiration and the one we just computed.
- msUntilExpiration = existingExpirationTime - currentTimeMs;
- if (expiration < msUntilExpiration) {
- root.expiresAt = expiration;
- }
- }
- newCallbackNode = scheduleCallback(
- priorityLevel,
- performConcurrentWorkOnRoot.bind(null, root),
- );
- } else {
- newCallbackNode = scheduleCallback(
- priorityLevel,
- performConcurrentWorkOnRoot.bind(null, root),
- );
- }
+ const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
+ newCallbackPriorityLevel,
+ );
+ newCallbackNode = scheduleCallback(
+ schedulerPriorityLevel,
+ performConcurrentWorkOnRoot.bind(null, root),
+ );
}
root.callbackId = newCallbackId;
@@ -744,18 +621,40 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
// Since we know we're in a React event, we can clear the current
// event time. The next update will compute a new event time.
currentEventTime = -1;
+ currentEventWipLanes = NoLanes;
+ currentEventPendingLanes = NoLanes;
// Determine the next expiration time to work on, using the fields stored
// on the root.
- let expirationTime = getNextRootExpirationTimeToWorkOn(root);
- if (isSameExpirationTime(expirationTime, (NoWork: ExpirationTimeOpaque))) {
+ let lanes = getNextLanes(
+ root,
+ root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
+ );
+ if (lanes === NoLanes) {
return null;
}
+ // Check if any work has expired.
+ const rootExpiresAt = root.expiresAt;
+ if (rootExpiresAt !== -1 && rootExpiresAt < now()) {
+ // Something expired. Flush synchronously until there's no expired
+ // work left.
+ // TODO: Should flush only the lanes that have expired, and maybe any lanes
+ // that are higher priority than that.
+ markRootExpired(root, lanes);
+ // This will schedule a synchronous callback.
+ ensureRootIsScheduled(root);
+ return null;
+ }
+ // Similar branch, but for Scheduler.
+ // TODO: This is only here to account for a Scheduler bug where `shouldYield`
+ // sometimes returns `true` even if `didTimeout` is true, which leads to
+ // an infinite loop. Once the bug in Scheduler is fixed, we can remove this,
+ // since we track expiration times ourselves.
if (didTimeout) {
- // The render task took too long to complete. Mark the root as expired to
+ // The Scheduler task took too long to complete. Mark the root as expired to
// prevent yielding to other tasks until this one finishes.
- markRootExpiredAtTime(root, expirationTime);
+ markRootExpired(root, lanes);
// This will schedule a synchronous callback.
ensureRootIsScheduled(root);
return null;
@@ -769,28 +668,37 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
flushPassiveEffects();
- let exitStatus = renderRootConcurrent(root, expirationTime);
+ let exitStatus = renderRootConcurrent(root, lanes);
- if (exitStatus !== RootIncomplete) {
+ if (
+ includesSomeLane(
+ workInProgressRootIncludedLanes,
+ workInProgressRootUpdatedLanes,
+ )
+ ) {
+ // The render included lanes that were updated during the render phase.
+ // For example, when unhiding a hidden tree, we include all the lanes
+ // that were previously skipped when the tree was hidden. That set of
+ // lanes is a superset of the lanes we started rendering with.
+ //
+ // So we'll throw out the current work and restart.
+ prepareFreshStack(root, NoLanes);
+ } else if (exitStatus !== RootIncomplete) {
if (exitStatus === RootErrored) {
- // If something threw an error, try rendering one more time. We'll
- // render synchronously to block concurrent data mutations, and we'll
- // render at Idle (or lower) so that all pending updates are included.
- // If it still fails after the second attempt, we'll give up and commit
- // the resulting tree.
- expirationTime = !isSameOrHigherPriority(
- (Idle: ExpirationTimeOpaque),
- expirationTime,
- )
- ? (Idle: ExpirationTimeOpaque)
- : expirationTime;
- exitStatus = renderRootSync(root, expirationTime);
+ // If something threw an error, try rendering one more time. We'll render
+ // synchronously to block concurrent data mutations, and we'll includes
+ // all pending updates are included. If it still fails after the second
+ // attempt, we'll give up and commit the resulting tree.
+ lanes = getLanesToRetrySynchronouslyOnError(root);
+ if (lanes !== NoLanes) {
+ exitStatus = renderRootSync(root, lanes);
+ }
}
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
- prepareFreshStack(root, expirationTime);
- markRootSuspendedAtTime(root, expirationTime);
+ prepareFreshStack(root, NoLanes);
+ markRootSuspended(root, lanes);
ensureRootIsScheduled(root);
throw fatalError;
}
@@ -799,11 +707,8 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
// or, if something suspended, wait to commit it after a timeout.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
- root.finishedExpirationTime_opaque = expirationTime;
- root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(
- finishedWork,
- );
- finishConcurrentRender(root, finishedWork, exitStatus, expirationTime);
+ root.finishedLanes = lanes;
+ finishConcurrentRender(root, finishedWork, exitStatus, lanes);
}
ensureRootIsScheduled(root);
@@ -815,12 +720,7 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
return null;
}
-function finishConcurrentRender(
- root,
- finishedWork,
- exitStatus,
- expirationTime,
-) {
+function finishConcurrentRender(root, finishedWork, exitStatus, lanes) {
switch (exitStatus) {
case RootIncomplete:
case RootFatalErrored: {
@@ -836,8 +736,7 @@ function finishConcurrentRender(
break;
}
case RootSuspended: {
- markRootSuspendedAtTime(root, expirationTime);
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
+ markRootSuspended(root, lanes);
// We have an acceptable loading state. We need to figure out if we
// should immediately commit it or wait a bit.
@@ -862,42 +761,18 @@ function finishConcurrentRender(
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
- if (workInProgressRootHasPendingPing) {
- const lastPingedTime = root.lastPingedTime_opaque;
- if (
- isSameExpirationTime(
- lastPingedTime,
- (NoWork: ExpirationTimeOpaque),
- ) ||
- isSameOrHigherPriority(lastPingedTime, expirationTime)
- ) {
- // This render was pinged but we didn't get to restart
- // earlier so try restarting now instead.
- root.lastPingedTime_opaque = expirationTime;
- prepareFreshStack(root, expirationTime);
- break;
- }
- }
-
- const nextTime = getNextRootExpirationTimeToWorkOn(root);
- if (
- !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
- !isSameExpirationTime(nextTime, expirationTime)
- ) {
+ const nextLanes = getNextLanes(root, NoLanes);
+ if (nextLanes !== NoLanes) {
// There's additional work on this root.
break;
}
- if (
- !isSameExpirationTime(
- lastSuspendedTime,
- (NoWork: ExpirationTimeOpaque),
- ) &&
- !isSameExpirationTime(lastSuspendedTime, expirationTime)
- ) {
+ const suspendedLanes = root.suspendedLanes;
+ if (!isSubsetOfLanes(suspendedLanes, lanes)) {
// We should prefer to render the fallback of at the last
// suspended level. Ping the last suspended level to try
// rendering it again.
- root.lastPingedTime_opaque = lastSuspendedTime;
+ // FIXME: What if the suspended lanes are Idle? Should not restart.
+ markRootPinged(root, suspendedLanes);
break;
}
@@ -916,8 +791,7 @@ function finishConcurrentRender(
break;
}
case RootSuspendedWithDelay: {
- markRootSuspendedAtTime(root, expirationTime);
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
+ markRootSuspended(root, lanes);
if (
// do not delay if we're inside an act() scope
@@ -925,42 +799,18 @@ function finishConcurrentRender(
) {
// We're suspended in a state that should be avoided. We'll try to
// avoid committing it for as long as the timeouts let us.
- if (workInProgressRootHasPendingPing) {
- const lastPingedTime = root.lastPingedTime_opaque;
- if (
- isSameExpirationTime(
- lastPingedTime,
- (NoWork: ExpirationTimeOpaque),
- ) ||
- isSameOrHigherPriority(lastPingedTime, expirationTime)
- ) {
- // This render was pinged but we didn't get to restart earlier
- // so try restarting now instead.
- root.lastPingedTime_opaque = expirationTime;
- prepareFreshStack(root, expirationTime);
- break;
- }
- }
-
- const nextTime = getNextRootExpirationTimeToWorkOn(root);
- if (
- !isSameExpirationTime(nextTime, (NoWork: ExpirationTimeOpaque)) &&
- !isSameExpirationTime(nextTime, expirationTime)
- ) {
+ const nextLanes = getNextLanes(root, NoLanes);
+ if (nextLanes !== NoLanes) {
// There's additional work on this root.
break;
}
- if (
- !isSameExpirationTime(
- lastSuspendedTime,
- (NoWork: ExpirationTimeOpaque),
- ) &&
- !isSameExpirationTime(lastSuspendedTime, expirationTime)
- ) {
+ const suspendedLanes = root.suspendedLanes;
+ if (!isSubsetOfLanes(suspendedLanes, lanes)) {
// We should prefer to render the fallback of at the last
// suspended level. Ping the last suspended level to try
// rendering it again.
- root.lastPingedTime_opaque = lastSuspendedTime;
+ // FIXME: What if the suspended lanes are Idle? Should not restart.
+ markRootPinged(root, suspendedLanes);
break;
}
@@ -1015,7 +865,7 @@ function finishConcurrentRender(
workInProgressRootCanSuspendUsingConfig,
);
if (msUntilTimeout > 10) {
- markRootSuspendedAtTime(root, expirationTime);
+ markRootSuspended(root, lanes);
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
@@ -1032,6 +882,16 @@ function finishConcurrentRender(
}
}
+function markRootSuspended(root, suspendedLanes) {
+ // When suspending, we should always exclude lanes that were pinged or (more
+ // rarely, since we try to avoid it) updated during the render phase.
+ // TODO: Lol maybe there's a better way to factor this besides this
+ // obnoxiously named function :)
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
+ suspendedLanes = removeLanes(suspendedLanes, workInProgressRootUpdatedLanes);
+ markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes);
+}
+
// This is the entry point for synchronous tasks that don't go
// through Scheduler
function performSyncWorkOnRoot(root) {
@@ -1042,50 +902,53 @@ function performSyncWorkOnRoot(root) {
flushPassiveEffects();
- const lastExpiredTime = root.lastExpiredTime_opaque;
-
- let expirationTime;
- if (!isSameExpirationTime(lastExpiredTime, (NoWork: ExpirationTimeOpaque))) {
- // There's expired work on this root. Check if we have a partial tree
- // that we can reuse.
+ let lanes;
+ let exitStatus;
+ if (
+ root === workInProgressRoot &&
+ includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
+ ) {
+ // There's a partial tree, and at least one of its lanes has expired. Finish
+ // rendering it before rendering the rest of the expired work.
+ lanes = workInProgressRootRenderLanes;
+ exitStatus = renderRootSync(root, lanes);
if (
- root === workInProgressRoot &&
- isSameOrHigherPriority(renderExpirationTime, lastExpiredTime)
+ includesSomeLane(
+ workInProgressRootIncludedLanes,
+ workInProgressRootUpdatedLanes,
+ )
) {
- // There's a partial tree with equal or greater than priority than the
- // expired level. Finish rendering it before rendering the rest of the
- // expired work.
- expirationTime = renderExpirationTime;
- } else {
- // Start a fresh tree.
- expirationTime = lastExpiredTime;
+ // The render included lanes that were updated during the render phase.
+ // For example, when unhiding a hidden tree, we include all the lanes
+ // that were previously skipped when the tree was hidden. That set of
+ // lanes is a superset of the lanes we started rendering with.
+ //
+ // Note that this only happens when part of the tree is rendered
+ // concurrently. If the whole tree is rendered synchronously, then there
+ // are no interleaved events.
+ lanes = getNextLanes(root, lanes);
+ exitStatus = renderRootSync(root, lanes);
}
} else {
- // There's no expired work. This must be a new, synchronous render.
- expirationTime = Sync;
+ lanes = getNextLanes(root, NoLanes);
+ exitStatus = renderRootSync(root, lanes);
}
- let exitStatus = renderRootSync(root, expirationTime);
-
if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
- // If something threw an error, try rendering one more time. We'll
- // render synchronously to block concurrent data mutations, and we'll
- // render at Idle (or lower) so that all pending updates are included.
- // If it still fails after the second attempt, we'll give up and commit
- // the resulting tree.
- expirationTime = !isSameOrHigherPriority(
- (Idle: ExpirationTimeOpaque),
- expirationTime,
- )
- ? (Idle: ExpirationTimeOpaque)
- : expirationTime;
- exitStatus = renderRootSync(root, expirationTime);
+ // If something threw an error, try rendering one more time. We'll render
+ // synchronously to block concurrent data mutations, and we'll includes
+ // all pending updates are included. If it still fails after the second
+ // attempt, we'll give up and commit the resulting tree.
+ lanes = getLanesToRetrySynchronouslyOnError(root);
+ if (lanes !== NoLanes) {
+ exitStatus = renderRootSync(root, lanes);
+ }
}
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
- prepareFreshStack(root, expirationTime);
- markRootSuspendedAtTime(root, expirationTime);
+ prepareFreshStack(root, NoLanes);
+ markRootSuspended(root, lanes);
ensureRootIsScheduled(root);
throw fatalError;
}
@@ -1094,8 +957,7 @@ function performSyncWorkOnRoot(root) {
// will commit it even if something suspended.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
- root.finishedExpirationTime_opaque = expirationTime;
- root.nextKnownPendingLevel_opaque = getRemainingExpirationTime(finishedWork);
+ root.finishedLanes = lanes;
commitRoot(root);
// Before exiting, make sure there's a callback scheduled for the next
@@ -1105,11 +967,8 @@ function performSyncWorkOnRoot(root) {
return null;
}
-export function flushRoot(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-) {
- markRootExpiredAtTime(root, expirationTime);
+export function flushRoot(root: FiberRoot, lanes: Lanes) {
+ markRootExpired(root, lanes);
ensureRootIsScheduled(root);
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
flushSyncCallbackQueue();
@@ -1164,8 +1023,8 @@ function flushPendingDiscreteUpdates() {
// immediately flush them.
const roots = rootsWithPendingDiscreteUpdates;
rootsWithPendingDiscreteUpdates = null;
- roots.forEach((expirationTime, root) => {
- markRootExpiredAtTime(root, expirationTime);
+ roots.forEach(root => {
+ markDiscreteUpdatesExpired(root);
ensureRootIsScheduled(root);
});
}
@@ -1278,22 +1137,23 @@ export function flushControlled(fn: () => mixed): void {
}
}
-export function pushRenderExpirationTime(
- fiber: Fiber,
- subtreeRenderTime: ExpirationTimeOpaque,
-) {
- pushToStack(renderExpirationTimeCursor, renderExpirationTime, fiber);
- renderExpirationTime = subtreeRenderTime;
+export function pushRenderLanes(fiber: Fiber, lanes: Lanes) {
+ pushToStack(subtreeRenderLanesCursor, subtreeRenderLanes, fiber);
+ subtreeRenderLanes = combineLanes(subtreeRenderLanes, lanes);
+ workInProgressRootIncludedLanes = combineLanes(
+ workInProgressRootIncludedLanes,
+ lanes,
+ );
}
-export function popRenderExpirationTime(fiber: Fiber) {
- renderExpirationTime = renderExpirationTimeCursor.current;
- popFromStack(renderExpirationTimeCursor, fiber);
+export function popRenderLanes(fiber: Fiber) {
+ subtreeRenderLanes = subtreeRenderLanesCursor.current;
+ popFromStack(subtreeRenderLanesCursor, fiber);
}
-function prepareFreshStack(root, expirationTime) {
+function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
root.finishedWork = null;
- root.finishedExpirationTime_opaque = NoWork;
+ root.finishedLanes = NoLanes;
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
@@ -1304,27 +1164,6 @@ function prepareFreshStack(root, expirationTime) {
cancelTimeout(timeoutHandle);
}
- // Check if there's a suspended level at lower priority.
- const lastSuspendedTime = root.lastSuspendedTime_opaque;
- if (
- !isSameExpirationTime(lastSuspendedTime, (NoWork: ExpirationTimeOpaque)) &&
- !isSameOrHigherPriority(lastSuspendedTime, expirationTime)
- ) {
- const lastPingedTime = root.lastPingedTime_opaque;
- // Make sure the suspended level is marked as pinged so that we return back
- // to it later, in case the render we're about to start gets aborted.
- // Generally we only reach this path via a ping, but we shouldn't assume
- // that will always be the case.
- // Note: This is defensive coding to prevent a pending commit from
- // being dropped without being rescheduled. It shouldn't be necessary.
- if (
- isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) ||
- !isSameOrHigherPriority(lastSuspendedTime, lastPingedTime)
- ) {
- root.lastPingedTime_opaque = lastSuspendedTime;
- }
- }
-
if (workInProgress !== null) {
let interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
@@ -1334,14 +1173,15 @@ function prepareFreshStack(root, expirationTime) {
}
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
- renderExpirationTime = expirationTime;
+ workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestProcessedEventTime = -1;
workInProgressRootLatestSuspenseTimeout = -1;
workInProgressRootCanSuspendUsingConfig = null;
- workInProgressRootNextUnprocessedUpdateTime = NoWork;
- workInProgressRootHasPendingPing = false;
+ workInProgressRootSkippedLanes = NoLanes;
+ workInProgressRootUpdatedLanes = NoLanes;
+ workInProgressRootPingedLanes = NoLanes;
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
@@ -1393,7 +1233,7 @@ function handleError(root, thrownValue): void {
erroredWork.return,
erroredWork,
thrownValue,
- renderExpirationTime,
+ workInProgressRootRenderLanes,
);
completeUnitOfWork(erroredWork);
} catch (yetAnotherThrownValue) {
@@ -1479,17 +1319,11 @@ export function markRenderEventTimeAndConfig(
}
}
-export function markUnprocessedUpdateTime(
- expirationTime: ExpirationTimeOpaque,
-): void {
- if (
- !isSameOrHigherPriority(
- workInProgressRootNextUnprocessedUpdateTime,
- expirationTime,
- )
- ) {
- workInProgressRootNextUnprocessedUpdateTime = expirationTime;
- }
+export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
+ workInProgressRootSkippedLanes = combineLanes(
+ lane,
+ workInProgressRootSkippedLanes,
+ );
}
export function renderDidSuspend(): void {
@@ -1506,23 +1340,21 @@ export function renderDidSuspendDelayIfPossible(): void {
workInProgressRootExitStatus = RootSuspendedWithDelay;
}
- // Check if there's a lower priority update somewhere else in the tree.
+ // Check if there are updates that we skipped tree that might have unblocked
+ // this render.
if (
- !isSameExpirationTime(
- workInProgressRootNextUnprocessedUpdateTime,
- (NoWork: ExpirationTimeOpaque),
- ) &&
- workInProgressRoot !== null
+ workInProgressRoot !== null &&
+ (hasUpdatePriority(workInProgressRootSkippedLanes) ||
+ hasUpdatePriority(workInProgressRootUpdatedLanes))
) {
- // Mark the current render as suspended, and then mark that there's a
- // pending update.
- // TODO: This should immediately interrupt the current render, instead
- // of waiting until the next time we yield.
- markRootSuspendedAtTime(workInProgressRoot, renderExpirationTime);
- markRootUpdatedAtTime(
- workInProgressRoot,
- workInProgressRootNextUnprocessedUpdateTime,
- );
+ // Mark the current render as suspended so that we switch to working on
+ // the updates that were skipped. Usually we only suspend at the end of
+ // the render phase.
+ // TODO: We should probably always mark the root as suspended immediately
+ // (inside this function), since by suspending at the end of the render
+ // phase introduces a potential mistake where we suspend lanes that were
+ // pinged or updated while we were rendering.
+ markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes);
}
}
@@ -1540,19 +1372,16 @@ export function renderHasNotSuspendedYet(): boolean {
return workInProgressRootExitStatus === RootIncomplete;
}
-function renderRootSync(root, expirationTime) {
+function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root);
- // If the root or expiration time have changed, throw out the existing stack
+ // If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
- if (
- root !== workInProgressRoot ||
- !isSameExpirationTime(expirationTime, renderExpirationTime)
- ) {
- prepareFreshStack(root, expirationTime);
- startWorkOnPendingInteractions(root, expirationTime);
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
+ prepareFreshStack(root, lanes);
+ startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
@@ -1584,6 +1413,7 @@ function renderRootSync(root, expirationTime) {
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
@@ -1597,19 +1427,16 @@ function workLoopSync() {
}
}
-function renderRootConcurrent(root, expirationTime) {
+function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root);
- // If the root or expiration time have changed, throw out the existing stack
+ // If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
- if (
- root !== workInProgressRoot ||
- !isSameExpirationTime(expirationTime, renderExpirationTime)
- ) {
- prepareFreshStack(root, expirationTime);
- startWorkOnPendingInteractions(root, expirationTime);
+ if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
+ prepareFreshStack(root, lanes);
+ startWorkOnPendingInteractions(root, lanes);
}
const prevInteractions = pushInteractions(root);
@@ -1638,6 +1465,7 @@ function renderRootConcurrent(root, expirationTime) {
// Completed the tree.
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
+ workInProgressRootRenderLanes = NoLanes;
// Return the final exit status.
return workInProgressRootExitStatus;
@@ -1662,10 +1490,10 @@ function performUnitOfWork(unitOfWork: Fiber): void {
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
- next = beginWork(current, unitOfWork, renderExpirationTime);
+ next = beginWork(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
- next = beginWork(current, unitOfWork, renderExpirationTime);
+ next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentDebugFiberInDEV();
@@ -1699,15 +1527,15 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
- next = completeWork(current, completedWork, renderExpirationTime);
+ next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
startProfilerTimer(completedWork);
- next = completeWork(current, completedWork, renderExpirationTime);
+ next = completeWork(current, completedWork, subtreeRenderLanes);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
resetCurrentDebugFiberInDEV();
- resetChildExpirationTime(completedWork);
+ resetChildLanes(completedWork);
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
@@ -1757,7 +1585,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
- const next = unwindWork(completedWork, renderExpirationTime);
+ const next = unwindWork(completedWork, subtreeRenderLanes);
// Because this fiber did not complete, don't reset its expiration time.
@@ -1813,31 +1641,21 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
}
}
-function getRemainingExpirationTime(fiber: Fiber) {
- const updateExpirationTime = fiber.expirationTime_opaque;
- const childExpirationTime = fiber.childExpirationTime_opaque;
- return !isSameOrHigherPriority(childExpirationTime, updateExpirationTime)
- ? updateExpirationTime
- : childExpirationTime;
-}
-
-function resetChildExpirationTime(completedWork: Fiber) {
+function resetChildLanes(completedWork: Fiber) {
if (
- !isSameExpirationTime(
- renderExpirationTime,
- (Never: ExpirationTimeOpaque),
- ) &&
- isSameExpirationTime(
- completedWork.childExpirationTime_opaque,
- (Never: ExpirationTimeOpaque),
- )
+ // TODO: Move this check out of the hot path by moving `resetChildLanes`
+ // to switch statement in `completeWork`.
+ (completedWork.tag === LegacyHiddenComponent ||
+ completedWork.tag === OffscreenComponent) &&
+ completedWork.memoizedState !== null &&
+ !includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))
) {
// The children of this component are hidden. Don't bubble their
// expiration times.
return;
}
- let newChildExpirationTime = NoWork;
+ let newChildLanes = NoLanes;
// Bubble up the earliest expiration time.
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
@@ -1859,24 +1677,10 @@ function resetChildExpirationTime(completedWork: Fiber) {
let child = completedWork.child;
while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime_opaque;
- const childChildExpirationTime = child.childExpirationTime_opaque;
- if (
- !isSameOrHigherPriority(
- newChildExpirationTime,
- childUpdateExpirationTime,
- )
- ) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (
- !isSameOrHigherPriority(
- newChildExpirationTime,
- childChildExpirationTime,
- )
- ) {
- newChildExpirationTime = childChildExpirationTime;
- }
+ newChildLanes = combineLanes(
+ newChildLanes,
+ combineLanes(child.lanes, child.childLanes),
+ );
if (shouldBubbleActualDurations) {
actualDuration += child.actualDuration;
}
@@ -1888,29 +1692,15 @@ function resetChildExpirationTime(completedWork: Fiber) {
} else {
let child = completedWork.child;
while (child !== null) {
- const childUpdateExpirationTime = child.expirationTime_opaque;
- const childChildExpirationTime = child.childExpirationTime_opaque;
- if (
- !isSameOrHigherPriority(
- newChildExpirationTime,
- childUpdateExpirationTime,
- )
- ) {
- newChildExpirationTime = childUpdateExpirationTime;
- }
- if (
- !isSameOrHigherPriority(
- newChildExpirationTime,
- childChildExpirationTime,
- )
- ) {
- newChildExpirationTime = childChildExpirationTime;
- }
+ newChildLanes = combineLanes(
+ newChildLanes,
+ combineLanes(child.lanes, child.childLanes),
+ );
child = child.sibling;
}
}
- completedWork.childExpirationTime_opaque = newChildExpirationTime;
+ completedWork.childLanes = newChildLanes;
}
function commitRoot(root) {
@@ -1940,12 +1730,12 @@ function commitRootImpl(root, renderPriorityLevel) {
);
const finishedWork = root.finishedWork;
- const expirationTime = root.finishedExpirationTime_opaque;
+ const lanes = root.finishedLanes;
if (finishedWork === null) {
return null;
}
root.finishedWork = null;
- root.finishedExpirationTime_opaque = NoWork;
+ root.finishedLanes = NoLanes;
invariant(
finishedWork !== root.current,
@@ -1956,7 +1746,7 @@ function commitRootImpl(root, renderPriorityLevel) {
// commitRoot never returns a continuation; it always finishes synchronously.
// So we can clear these now to allow a new callback to be scheduled.
root.callbackNode = null;
- root.callbackId = NoWork;
+ root.callbackId = NoLanes;
// TODO: Use LanePriority instead of SchedulerPriority
if (renderPriorityLevel < ImmediateSchedulerPriority) {
// If this was a concurrent render, we can reset the expiration time.
@@ -1965,26 +1755,19 @@ function commitRootImpl(root, renderPriorityLevel) {
// Update the first and last pending times on this root. The new first
// pending time is whatever is left on the root fiber.
- const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
- finishedWork,
- );
- markRootFinishedAtTime(
- root,
- expirationTime,
- remainingExpirationTimeBeforeCommit,
+ let remainingLanes = combineLanes(
+ finishedWork.lanes,
+ finishedWork.childLanes,
);
+ markRootFinished(root, remainingLanes);
// Clear already finished discrete updates in case that a later call of
// `flushDiscreteUpdates` starts a useless render pass which may cancels
// a scheduled timeout.
if (rootsWithPendingDiscreteUpdates !== null) {
- const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (
- lastDiscreteTime !== undefined &&
- !isSameOrHigherPriority(
- remainingExpirationTimeBeforeCommit,
- lastDiscreteTime,
- )
+ !hasDiscreteLanes(remainingLanes) &&
+ rootsWithPendingDiscreteUpdates.has(root)
) {
rootsWithPendingDiscreteUpdates.delete(root);
}
@@ -1994,7 +1777,7 @@ function commitRootImpl(root, renderPriorityLevel) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
- renderExpirationTime = NoWork;
+ workInProgressRootRenderLanes = NoLanes;
} else {
// This indicates that the last root we worked on is not the same one that
// we're committing now. This most commonly happens when a suspended root
@@ -2112,13 +1895,7 @@ function commitRootImpl(root, renderPriorityLevel) {
nextEffect = firstEffect;
do {
if (__DEV__) {
- invokeGuardedCallback(
- null,
- commitLayoutEffects,
- null,
- root,
- expirationTime,
- );
+ invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
@@ -2127,7 +1904,7 @@ function commitRootImpl(root, renderPriorityLevel) {
}
} else {
try {
- commitLayoutEffects(root, expirationTime);
+ commitLayoutEffects(root, lanes);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
@@ -2164,7 +1941,7 @@ function commitRootImpl(root, renderPriorityLevel) {
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
- pendingPassiveEffectsExpirationTime = expirationTime;
+ pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// We are done with the effect chain at this point so let's clear the
@@ -2178,14 +1955,11 @@ function commitRootImpl(root, renderPriorityLevel) {
}
}
+ // Read this again, since an effect might have updated it
+ remainingLanes = root.pendingLanes;
+
// Check if there's remaining work on this root
- const remainingExpirationTime = root.firstPendingTime_opaque;
- if (
- !isSameExpirationTime(
- remainingExpirationTime,
- (NoWork: ExpirationTimeOpaque),
- )
- ) {
+ if (remainingLanes !== NoLanes) {
if (enableSchedulerTracing) {
if (spawnedWorkDuringRender !== null) {
const expirationTimes = spawnedWorkDuringRender;
@@ -2198,7 +1972,7 @@ function commitRootImpl(root, renderPriorityLevel) {
);
}
}
- schedulePendingInteractions(root, remainingExpirationTime);
+ schedulePendingInteractions(root, remainingLanes);
}
} else {
// If there's no remaining work, we can clear the set of already failed
@@ -2212,13 +1986,11 @@ function commitRootImpl(root, renderPriorityLevel) {
// Otherwise, we'll wait until after the passive effects are flushed.
// Wait to do this until after remaining work has been scheduled,
// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
- finishPendingInteractions(root, expirationTime);
+ finishPendingInteractions(root, lanes);
}
}
- if (
- isSameExpirationTime(remainingExpirationTime, (Sync: ExpirationTimeOpaque))
- ) {
+ if (remainingLanes === SyncLane) {
// Count the number of times the root synchronously re-renders without
// finishing. If there are too many, it indicates an infinite update loop.
if (root === rootWithNestedUpdates) {
@@ -2366,10 +2138,7 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
}
}
-function commitLayoutEffects(
- root: FiberRoot,
- committedExpirationTime: ExpirationTimeOpaque,
-) {
+function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
// TODO: Should probably move the bulk of this function to commitWork.
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
@@ -2378,12 +2147,7 @@ function commitLayoutEffects(
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
- commitLayoutEffectOnFiber(
- root,
- current,
- nextEffect,
- committedExpirationTime,
- );
+ commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
if (effectTag & Ref) {
@@ -2471,9 +2235,9 @@ function flushPassiveEffectsImpl() {
}
const root = rootWithPendingPassiveEffects;
- const expirationTime = pendingPassiveEffectsExpirationTime;
+ const lanes = pendingPassiveEffectsLanes;
rootWithPendingPassiveEffects = null;
- pendingPassiveEffectsExpirationTime = NoWork;
+ pendingPassiveEffectsLanes = NoLanes;
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
@@ -2651,7 +2415,7 @@ function flushPassiveEffectsImpl() {
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set));
- finishPendingInteractions(root, expirationTime);
+ finishPendingInteractions(root, lanes);
}
if (__DEV__) {
@@ -2699,19 +2463,12 @@ function captureCommitPhaseErrorOnRoot(
error: mixed,
) {
const errorInfo = createCapturedValue(error, sourceFiber);
- const update = createRootErrorUpdate(
- rootFiber,
- errorInfo,
- (Sync: ExpirationTimeOpaque),
- );
+ const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane));
enqueueUpdate(rootFiber, update);
- const root = markUpdateTimeFromFiberToRoot(
- rootFiber,
- (Sync: ExpirationTimeOpaque),
- );
+ const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane));
if (root !== null) {
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
+ schedulePendingInteractions(root, SyncLane);
}
}
@@ -2740,16 +2497,13 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
const update = createClassErrorUpdate(
fiber,
errorInfo,
- (Sync: ExpirationTimeOpaque),
+ (SyncLane: Lane),
);
enqueueUpdate(fiber, update);
- const root = markUpdateTimeFromFiberToRoot(
- fiber,
- (Sync: ExpirationTimeOpaque),
- );
+ const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane));
if (root !== null) {
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, (Sync: ExpirationTimeOpaque));
+ schedulePendingInteractions(root, SyncLane);
}
return;
}
@@ -2761,7 +2515,7 @@ export function captureCommitPhaseError(sourceFiber: Fiber, error: mixed) {
export function pingSuspendedRoot(
root: FiberRoot,
wakeable: Wakeable,
- suspendedTime: ExpirationTimeOpaque,
+ pingedLanes: Lanes,
) {
const pingCache = root.pingCache;
if (pingCache !== null) {
@@ -2770,9 +2524,14 @@ export function pingSuspendedRoot(
pingCache.delete(wakeable);
}
+ // TODO: Instead of passing the current time as an argument, we should read it
+ // only if something new was pinged. Currently that check happens inside
+ // `markRootPinged`.
+ markRootPinged(root, pingedLanes);
+
if (
workInProgressRoot === root &&
- isSameExpirationTime(renderExpirationTime, suspendedTime)
+ isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
) {
// Received a ping at the same priority level at which we're currently
// rendering. We might want to restart this render. This should mirror
@@ -2793,69 +2552,52 @@ export function pingSuspendedRoot(
workInProgressRootLatestProcessedEventTime === -1 &&
now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
) {
- // Restart from the root. Don't need to schedule a ping because
- // we're already working on this tree.
- prepareFreshStack(root, renderExpirationTime);
+ // Restart from the root.
+ prepareFreshStack(root, NoLanes);
} else {
// Even though we can't restart right now, we might get an
// opportunity later. So we mark this render as having a ping.
- workInProgressRootHasPendingPing = true;
+ workInProgressRootPingedLanes = combineLanes(
+ workInProgressRootPingedLanes,
+ pingedLanes,
+ );
}
- return;
- }
-
- if (!isRootSuspendedAtTime(root, suspendedTime)) {
- // The root is no longer suspended at this time.
- return;
}
- const lastPingedTime = root.lastPingedTime_opaque;
- if (
- !isSameExpirationTime(lastPingedTime, (NoWork: ExpirationTimeOpaque)) &&
- !isSameOrHigherPriority(lastPingedTime, suspendedTime)
- ) {
- // There's already a lower priority ping scheduled.
- return;
- }
-
- // Mark the time at which this ping was scheduled.
- root.lastPingedTime_opaque = suspendedTime;
-
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, suspendedTime);
+ schedulePendingInteractions(root, pingedLanes);
}
-function retryTimedOutBoundary(
- boundaryFiber: Fiber,
- retryTime: ExpirationTimeOpaque,
-) {
+function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
// The boundary fiber (a Suspense component or SuspenseList component)
// previously was rendered in its fallback state. One of the promises that
// suspended it has resolved, which means at least part of the tree was
// likely unblocked. Try rendering again, at a new expiration time.
- if (isSameExpirationTime(retryTime, (NoWork: ExpirationTimeOpaque))) {
+ if (retryLane === NoLane) {
const suspenseConfig = null; // Retries don't carry over the already committed update.
- retryTime = requestUpdateExpirationTime(boundaryFiber, suspenseConfig);
+ // TODO: Should retries get their own lane? Maybe it could share with
+ // transitions.
+ retryLane = requestUpdateLane(boundaryFiber, suspenseConfig);
}
// TODO: Special case idle priority?
- const root = markUpdateTimeFromFiberToRoot(boundaryFiber, retryTime);
+ const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
if (root !== null) {
ensureRootIsScheduled(root);
- schedulePendingInteractions(root, retryTime);
+ schedulePendingInteractions(root, retryLane);
}
}
export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
- let retryTime = NoWork;
+ let retryLane = NoLane;
if (suspenseState !== null) {
- retryTime = suspenseState.retryTime;
+ retryLane = suspenseState.retryLane;
}
- retryTimedOutBoundary(boundaryFiber, retryTime);
+ retryTimedOutBoundary(boundaryFiber, retryLane);
}
export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
- let retryTime = NoWork; // Default
+ let retryLane = NoLane; // Default
let retryCache: WeakSet | Set | null;
if (enableSuspenseServerRenderer) {
switch (boundaryFiber.tag) {
@@ -2863,7 +2605,7 @@ export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
retryCache = boundaryFiber.stateNode;
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
if (suspenseState !== null) {
- retryTime = suspenseState.retryTime;
+ retryLane = suspenseState.retryLane;
}
break;
case SuspenseListComponent:
@@ -2886,7 +2628,7 @@ export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
retryCache.delete(wakeable);
}
- retryTimedOutBoundary(boundaryFiber, retryTime);
+ retryTimedOutBoundary(boundaryFiber, retryLane);
}
// Computes the next Just Noticeable Difference (JND) boundary.
@@ -3054,7 +2796,7 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
let beginWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
const dummyFiber = null;
- beginWork = (current, unitOfWork, expirationTime) => {
+ beginWork = (current, unitOfWork, lanes) => {
// If a component throws an error, we replay it again in a synchronously
// dispatched event, so that the debugger will treat it as an uncaught
// error See ReactErrorUtils for more information.
@@ -3066,7 +2808,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
unitOfWork,
);
try {
- return originalBeginWork(current, unitOfWork, expirationTime);
+ return originalBeginWork(current, unitOfWork, lanes);
} catch (originalError) {
if (
originalError !== null &&
@@ -3102,7 +2844,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
null,
current,
unitOfWork,
- expirationTime,
+ lanes,
);
if (hasCaughtError()) {
@@ -3316,31 +3058,28 @@ export function warnIfUnmockedScheduler(fiber: Fiber) {
}
}
-function computeThreadID(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-) {
+function computeThreadID(root: FiberRoot, lane: Lane | Lanes) {
// Interaction threads are unique per root and expiration time.
// NOTE: Intentionally unsound cast. All that matters is that it's a number
// and it represents a batch of work. Could make a helper function instead,
// but meh this is fine for now.
- return (expirationTime: any) * 1000 + root.interactionThreadID;
+ return (lane: any) * 1000 + root.interactionThreadID;
}
-export function markSpawnedWork(expirationTime: ExpirationTimeOpaque) {
+export function markSpawnedWork(lane: Lane | Lanes) {
if (!enableSchedulerTracing) {
return;
}
if (spawnedWorkDuringRender === null) {
- spawnedWorkDuringRender = [expirationTime];
+ spawnedWorkDuringRender = [lane];
} else {
- spawnedWorkDuringRender.push(expirationTime);
+ spawnedWorkDuringRender.push(lane);
}
}
function scheduleInteractions(
root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane | Lanes,
interactions: Set,
) {
if (!enableSchedulerTracing) {
@@ -3349,7 +3088,7 @@ function scheduleInteractions(
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap_new;
- const pendingInteractions = pendingInteractionMap.get(expirationTime);
+ const pendingInteractions = pendingInteractionMap.get(lane);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
@@ -3360,7 +3099,7 @@ function scheduleInteractions(
pendingInteractions.add(interaction);
});
} else {
- pendingInteractionMap.set(expirationTime, new Set(interactions));
+ pendingInteractionMap.set(lane, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
@@ -3370,16 +3109,13 @@ function scheduleInteractions(
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
- const threadID = computeThreadID(root, expirationTime);
+ const threadID = computeThreadID(root, lane);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
-function schedulePendingInteractions(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-) {
+function schedulePendingInteractions(root: FiberRoot, lane: Lane | Lanes) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
@@ -3387,13 +3123,10 @@ function schedulePendingInteractions(
return;
}
- scheduleInteractions(root, expirationTime, __interactionsRef.current);
+ scheduleInteractions(root, lane, __interactionsRef.current);
}
-function startWorkOnPendingInteractions(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-) {
+function startWorkOnPendingInteractions(root: FiberRoot, lanes: Lanes) {
// This is called when new work is started on a root.
if (!enableSchedulerTracing) {
return;
@@ -3404,8 +3137,8 @@ function startWorkOnPendingInteractions(
// work triggered during the render phase will be associated with it.
const interactions: Set = new Set();
root.pendingInteractionMap_new.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- if (isSameOrHigherPriority(scheduledExpirationTime, expirationTime)) {
+ (scheduledInteractions, scheduledLane) => {
+ if (includesSomeLane(lanes, scheduledLane)) {
scheduledInteractions.forEach(interaction =>
interactions.add(interaction),
);
@@ -3423,7 +3156,7 @@ function startWorkOnPendingInteractions(
if (interactions.size > 0) {
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
- const threadID = computeThreadID(root, expirationTime);
+ const threadID = computeThreadID(root, lanes);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
@@ -3436,19 +3169,20 @@ function startWorkOnPendingInteractions(
}
}
-function finishPendingInteractions(root, committedExpirationTime) {
+function finishPendingInteractions(root, committedLanes) {
if (!enableSchedulerTracing) {
return;
}
- const earliestRemainingTimeAfterCommit = root.firstPendingTime_opaque;
+ const remainingLanesAfterCommit = root.pendingLanes;
let subscriber;
try {
subscriber = __subscriberRef.current;
if (subscriber !== null && root.memoizedInteractions.size > 0) {
- const threadID = computeThreadID(root, committedExpirationTime);
+ // FIXME: More than one lane can finish in a single commit.
+ const threadID = computeThreadID(root, committedLanes);
subscriber.onWorkStopped(root.memoizedInteractions, threadID);
}
} catch (error) {
@@ -3461,36 +3195,29 @@ function finishPendingInteractions(root, committedExpirationTime) {
// Unless the render was suspended or cascading work was scheduled,
// In which case– leave pending interactions until the subsequent render.
const pendingInteractionMap = root.pendingInteractionMap_new;
- pendingInteractionMap.forEach(
- (scheduledInteractions, scheduledExpirationTime) => {
- // Only decrement the pending interaction count if we're done.
- // If there's still work at the current priority,
- // That indicates that we are waiting for suspense data.
- if (
- !isSameOrHigherPriority(
- earliestRemainingTimeAfterCommit,
- scheduledExpirationTime,
- )
- ) {
- pendingInteractionMap.delete(scheduledExpirationTime);
+ pendingInteractionMap.forEach((scheduledInteractions, lane) => {
+ // Only decrement the pending interaction count if we're done.
+ // If there's still work at the current priority,
+ // That indicates that we are waiting for suspense data.
+ if (!includesSomeLane(remainingLanesAfterCommit, lane)) {
+ pendingInteractionMap.delete(lane);
- scheduledInteractions.forEach(interaction => {
- interaction.__count--;
+ scheduledInteractions.forEach(interaction => {
+ interaction.__count--;
- if (subscriber !== null && interaction.__count === 0) {
- try {
- subscriber.onInteractionScheduledWorkCompleted(interaction);
- } catch (error) {
- // If the subscriber throws, rethrow it in a separate task
- scheduleCallback(ImmediateSchedulerPriority, () => {
- throw error;
- });
- }
+ if (subscriber !== null && interaction.__count === 0) {
+ try {
+ subscriber.onInteractionScheduledWorkCompleted(interaction);
+ } catch (error) {
+ // If the subscriber throws, rethrow it in a separate task
+ scheduleCallback(ImmediateSchedulerPriority, () => {
+ throw error;
+ });
}
- });
- }
- },
- );
+ }
+ });
+ }
+ });
}
}
diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js
index edca38409314f..2b12735c8c58f 100644
--- a/packages/react-reconciler/src/ReactInternalTypes.js
+++ b/packages/react-reconciler/src/ReactInternalTypes.js
@@ -23,7 +23,7 @@ import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {SideEffectTag} from './ReactSideEffectTags';
import type {ExpirationTime} from './ReactFiberExpirationTime.old';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lane, Lanes} from './ReactFiberLane';
import type {HookType} from './ReactFiberHooks.old';
import type {RootTag} from './ReactRootTags';
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
@@ -52,7 +52,7 @@ export type Dependencies_old = {
};
export type Dependencies_new = {
- expirationTime: ExpirationTimeOpaque,
+ lanes: Lanes,
firstContext: ContextDependency | null,
responders: Map<
ReactEventResponder,
@@ -148,16 +148,13 @@ export type Fiber = {|
firstEffect: Fiber | null,
lastEffect: Fiber | null,
- // Represents a time in the future by which this work should be completed.
- // Does not include work found in its subtree.
+ // Only used by old reconciler
expirationTime: ExpirationTime,
-
- // This is used to quickly determine if a subtree has no pending changes.
childExpirationTime: ExpirationTime,
// Only used by new reconciler
- expirationTime_opaque: ExpirationTimeOpaque,
- childExpirationTime_opaque: ExpirationTimeOpaque,
+ lanes: Lanes,
+ childLanes: Lanes,
// This is a pooled version of a Fiber. Every fiber that gets updated will
// eventually have a pair. There are cases when we can clean up pairs to save
@@ -255,8 +252,7 @@ type BaseFiberRootProperties = {|
// Represents the next task that the root should work on, or the current one
// if it's already working.
- // TODO: In the new system, this will be a Lanes bitmask.
- callbackId: ExpirationTimeOpaque,
+ callbackId: Lanes,
// Whether the currently scheduled task for this root is synchronous or
// batched/concurrent. We have to track this because Scheduler does not
// support synchronous tasks, so we put those on a separate queue. So you
@@ -269,17 +265,13 @@ type BaseFiberRootProperties = {|
// timestamp, in milliseconds.
expiresAt: number,
- // Same as corresponding fields in the old reconciler, but opaque. These will
- // become bitmasks.
- finishedExpirationTime_opaque: ExpirationTimeOpaque,
- firstPendingTime_opaque: ExpirationTimeOpaque,
- lastPendingTime_opaque: ExpirationTimeOpaque,
- firstSuspendedTime_opaque: ExpirationTimeOpaque,
- lastSuspendedTime_opaque: ExpirationTimeOpaque,
- nextKnownPendingLevel_opaque: ExpirationTimeOpaque,
- lastPingedTime_opaque: ExpirationTimeOpaque,
- lastExpiredTime_opaque: ExpirationTimeOpaque,
- mutableSourceLastPendingUpdateTime_opaque: ExpirationTimeOpaque,
+ pendingLanes: Lanes,
+ suspendedLanes: Lanes,
+ pingedLanes: Lanes,
+ expiredLanes: Lanes,
+ mutableReadLanes: Lanes,
+
+ finishedLanes: Lanes,
|};
// The following attributes are only used by interaction tracing builds.
@@ -289,7 +281,7 @@ type BaseFiberRootProperties = {|
type ProfilingOnlyFiberRootProperties = {|
interactionThreadID: number,
memoizedInteractions: Set,
- pendingInteractionMap_new: Map>,
+ pendingInteractionMap_new: Map>,
pendingInteractionMap_old: Map>,
|};
diff --git a/packages/react-reconciler/src/ReactMutableSource.new.js b/packages/react-reconciler/src/ReactMutableSource.new.js
index bc11adcd33050..be7bb4dfdf594 100644
--- a/packages/react-reconciler/src/ReactMutableSource.new.js
+++ b/packages/react-reconciler/src/ReactMutableSource.new.js
@@ -7,16 +7,9 @@
* @flow
*/
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
-import type {FiberRoot} from './ReactInternalTypes';
import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
-import {
- NoWork,
- isSameOrHigherPriority,
- isSameExpirationTime,
-} from './ReactFiberExpirationTime.new';
// Work in progress version numbers only apply to a single render,
// and should be reset before starting a new render.
@@ -30,44 +23,6 @@ if (__DEV__) {
rendererSigil = {};
}
-export function clearPendingUpdates(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): void {
- if (
- isSameOrHigherPriority(
- root.mutableSourceLastPendingUpdateTime_opaque,
- expirationTime,
- )
- ) {
- // All updates for this source have been processed.
- root.mutableSourceLastPendingUpdateTime_opaque = NoWork;
- }
-}
-
-export function getLastPendingExpirationTime(
- root: FiberRoot,
-): ExpirationTimeOpaque {
- return root.mutableSourceLastPendingUpdateTime_opaque;
-}
-
-export function setPendingExpirationTime(
- root: FiberRoot,
- expirationTime: ExpirationTimeOpaque,
-): void {
- const mutableSourceLastPendingUpdateTime =
- root.mutableSourceLastPendingUpdateTime_opaque;
- if (
- isSameExpirationTime(
- mutableSourceLastPendingUpdateTime,
- (NoWork: ExpirationTimeOpaque),
- ) ||
- !isSameOrHigherPriority(expirationTime, mutableSourceLastPendingUpdateTime)
- ) {
- root.mutableSourceLastPendingUpdateTime_opaque = expirationTime;
- }
-}
-
export function markSourceAsDirty(mutableSource: MutableSource): void {
if (isPrimaryRenderer) {
workInProgressPrimarySources.push(mutableSource);
diff --git a/packages/react-reconciler/src/ReactUpdateQueue.new.js b/packages/react-reconciler/src/ReactUpdateQueue.new.js
index 695dcfecda1d1..c59f9beb40eed 100644
--- a/packages/react-reconciler/src/ReactUpdateQueue.new.js
+++ b/packages/react-reconciler/src/ReactUpdateQueue.new.js
@@ -85,14 +85,15 @@
// resources, but the final state is always the same.
import type {Fiber} from './ReactInternalTypes';
-import type {ExpirationTimeOpaque} from './ReactFiberExpirationTime.new';
+import type {Lanes, Lane} from './ReactFiberLane';
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
import {
- NoWork,
- Sync,
- isSameOrHigherPriority,
-} from './ReactFiberExpirationTime.new';
+ NoLane,
+ NoLanes,
+ includesSomeLane,
+ combineLanes,
+} from './ReactFiberLane';
import {
enterDisallowedContextReadInDEV,
exitDisallowedContextReadInDEV,
@@ -104,7 +105,7 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags
import {StrictMode} from './ReactTypeOfMode';
import {
markRenderEventTimeAndConfig,
- markUnprocessedUpdateTime,
+ markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.new';
import invariant from 'shared/invariant';
@@ -115,7 +116,7 @@ export type Update = {|
// TODO: Temporary field. Will remove this by storing a map of
// transition -> event time on the root.
eventTime: number,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane,
suspenseConfig: null | SuspenseConfig,
tag: 0 | 1 | 2 | 3,
@@ -192,12 +193,12 @@ export function cloneUpdateQueue(
export function createUpdate(
eventTime: number,
- expirationTime: ExpirationTimeOpaque,
+ lane: Lane,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
const update: Update<*> = {
eventTime,
- expirationTime,
+ lane,
suspenseConfig,
tag: UpdateState,
@@ -272,7 +273,7 @@ export function enqueueCapturedUpdate(
do {
const clone: Update = {
eventTime: update.eventTime,
- expirationTime: update.expirationTime,
+ lane: update.lane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
@@ -410,7 +411,7 @@ export function processUpdateQueue(
workInProgress: Fiber,
props: any,
instance: any,
- renderExpirationTime: ExpirationTimeOpaque,
+ renderLanes: Lanes,
): void {
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue = (workInProgress.updateQueue: any);
@@ -467,7 +468,9 @@ export function processUpdateQueue(
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
- let newExpirationTime = NoWork;
+ // TODO: Don't need to accumulate this. Instead, we can remove renderLanes
+ // from the original lanes.
+ let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
@@ -475,15 +478,15 @@ export function processUpdateQueue(
let update = firstBaseUpdate;
do {
+ const updateLane = update.lane;
const updateEventTime = update.eventTime;
- const updateExpirationTime = update.expirationTime;
- if (!isSameOrHigherPriority(updateExpirationTime, renderExpirationTime)) {
+ if (!includesSomeLane(renderLanes, updateLane) && updateLane !== NoLane) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update = {
eventTime: updateEventTime,
- expirationTime: updateExpirationTime,
+ lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
@@ -499,16 +502,14 @@ export function processUpdateQueue(
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
- if (!isSameOrHigherPriority(newExpirationTime, updateExpirationTime)) {
- newExpirationTime = updateExpirationTime;
- }
+ newLanes = combineLanes(newLanes, updateLane);
} else {
// This update does have sufficient priority.
if (newLastBaseUpdate !== null) {
const clone: Update = {
eventTime: updateEventTime,
- expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
+ lane: NoLane, // This update is going to be committed so we never want uncommit it.
suspenseConfig: update.suspenseConfig,
tag: update.tag,
@@ -583,8 +584,8 @@ export function processUpdateQueue(
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
- markUnprocessedUpdateTime(newExpirationTime);
- workInProgress.expirationTime_opaque = newExpirationTime;
+ markSkippedUpdateLanes(newLanes);
+ workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
index 0e6c1d7aefc7f..90a327d7345fb 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
@@ -170,22 +170,32 @@ describe('ReactIncrementalUpdates', () => {
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
- expect(Scheduler).toFlushAndYield([
- 'a',
- 'b',
- 'c',
-
- // e, f, and g are in a separate batch from a, b, and c because they
- // were scheduled in the middle of a render
- 'e',
- 'f',
- 'g',
-
- 'd',
- 'e',
- 'f',
- 'g',
- ]);
+ expect(Scheduler).toFlushAndYield(
+ gate(flags =>
+ flags.new
+ ? ['a', 'b', 'c', 'd', 'e', 'f', 'g']
+ : [
+ 'a',
+ 'b',
+ 'c',
+
+ // The old reconciler has a quirk where `d` has slightly lower
+ // priority than `g`, because it was scheduled in the middle of a
+ // render. This is an implementation detail, but I've left the
+ // test in this branch as-is since this was written so long ago.
+ // This first render does not include d.
+ 'e',
+ 'f',
+ 'g',
+
+ // This second render does.
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ],
+ ),
+ );
expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]);
});
@@ -244,22 +254,32 @@ describe('ReactIncrementalUpdates', () => {
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
- expect(Scheduler).toFlushAndYield([
- 'a',
- 'b',
- 'c',
-
- // e, f, and g are in a separate batch from a, b, and c because they
- // were scheduled in the middle of a render
- 'e',
- 'f',
- 'g',
-
- 'd',
- 'e',
- 'f',
- 'g',
- ]);
+ expect(Scheduler).toFlushAndYield(
+ gate(flags =>
+ flags.new
+ ? ['a', 'b', 'c', 'd', 'e', 'f', 'g']
+ : [
+ 'a',
+ 'b',
+ 'c',
+
+ // The old reconciler has a quirk where `d` has slightly lower
+ // priority than `g`, because it was scheduled in the middle of a
+ // render. This is an implementation detail, but I've left the
+ // test in this branch as-is since this was written so long ago.
+ // This first render does not include d.
+ 'e',
+ 'f',
+ 'g',
+
+ // This second render does.
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ],
+ ),
+ );
expect(ReactNoop.getChildren()).toEqual([span('fg')]);
});
@@ -348,7 +368,7 @@ describe('ReactIncrementalUpdates', () => {
expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
});
- it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
+ it('updates triggered from inside a class setState updater', () => {
let instance;
class Foo extends React.Component {
state = {};
@@ -372,12 +392,26 @@ describe('ReactIncrementalUpdates', () => {
});
expect(() =>
- expect(Scheduler).toFlushAndYield([
- 'setState updater',
- // Update b is enqueued with the same priority as update a, so it should
- // be flushed in the same commit.
- 'render',
- ]),
+ expect(Scheduler).toFlushAndYield(
+ gate(flags =>
+ flags.new
+ ? [
+ 'setState updater',
+ // In the new reconciler, updates inside the render phase are
+ // treated as if they came from an event, so the update gets
+ // shifted to a subsequent render.
+ 'render',
+ 'render',
+ ]
+ : [
+ 'setState updater',
+ // In the old reconciler, updates in the render phase receive
+ // the currently rendering expiration time, so the update
+ // flushes immediately in the same render.
+ 'render',
+ ],
+ ),
+ ),
).toErrorDev(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
@@ -391,7 +425,19 @@ describe('ReactIncrementalUpdates', () => {
this.setState({a: 'a'});
return {b: 'b'};
});
- expect(Scheduler).toFlushAndYield(['render']);
+ expect(Scheduler).toFlushAndYield(
+ gate(flags =>
+ flags.new
+ ? // In the new reconciler, updates inside the render phase are
+ // treated as if they came from an event, so the update gets shifted
+ // to a subsequent render.
+ ['render', 'render']
+ : // In the old reconciler, updates in the render phase receive
+ // the currently rendering expiration time, so the update flushes
+ // immediately in the same render.
+ ['render'],
+ ),
+ );
});
it('getDerivedStateFromProps should update base state of updateQueue (based on product bug)', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 85e5d4b2b58c4..89f9c08e89760 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -322,6 +322,7 @@ describe('ReactSuspense', () => {
},
);
+ // @gate experimental
it(
'interrupts current render when something suspends with a ' +
"delay and we've already skipped over a lower priority update in " +
@@ -360,9 +361,14 @@ describe('ReactSuspense', () => {
// Do a bit of work
expect(Scheduler).toFlushAndYieldThrough(['A1']);
- // Schedule another update. This will get bumped into a different batch
- // because we're already in the middle of rendering.
- root.update();
+ // Schedule another update. This will have lower priority because it's
+ // a transition.
+ React.unstable_withSuspenseConfig(
+ () => {
+ root.update();
+ },
+ {timeoutMs: 10000},
+ );
// Interrupt to trigger a restart.
interrupt();
@@ -389,6 +395,7 @@ describe('ReactSuspense', () => {
},
);
+ // @gate experimental
it(
'interrupts current render when something suspends with a ' +
"delay and we've already bailed out lower priority update in " +
@@ -450,16 +457,20 @@ describe('ReactSuspense', () => {
setShouldSuspend(true);
// Need to move into the next async bucket.
- Scheduler.unstable_advanceTime(1000);
// Do a bit of work, then interrupt to trigger a restart.
expect(Scheduler).toFlushAndYieldThrough(['A']);
interrupt();
// Should not have committed loading state
expect(root).toMatchRenderedOutput('ABC');
- // Schedule another update. This will have lower priority because of
- // the interrupt trick above.
- setShouldHideInParent(true);
+ // Schedule another update. This will have lower priority because it's
+ // a transition.
+ React.unstable_withSuspenseConfig(
+ () => {
+ setShouldHideInParent(true);
+ },
+ {timeoutMs: 10000},
+ );
expect(Scheduler).toFlushAndYieldThrough([
// Should have restarted the first update, because of the interruption
diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json
index 6489bd5687bb1..3d302e784018e 100644
--- a/scripts/error-codes/codes.json
+++ b/scripts/error-codes/codes.json
@@ -354,5 +354,8 @@
"354": "getInspectorDataForViewAtPoint() is not available in production.",
"355": "The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. Do not read the value directly.",
"356": "Could not read the cache.",
- "357": "The current renderer does not support React Scopes. This error is likely caused by a bug in React. Please file an issue."
+ "357": "The current renderer does not support React Scopes. This error is likely caused by a bug in React. Please file an issue.",
+ "358": "Invalid update priority: %s. This is a bug in React.",
+ "359": "Invalid transition priority: %s. This is a bug in React.",
+ "360": "Invalid lane: %s. This is a bug in React."
}