diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 13a0f49105..d055149a09 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -18,7 +18,7 @@ import type { Fiber, TransitionTracingCallbacks, } from 'react-reconciler/src/ReactInternalTypes'; -import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue'; +import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue.new'; import type {ReactNodeList} from 'shared/ReactTypes'; import type {RootTag} from 'react-reconciler/src/ReactRootTags'; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index f1f5913b01..4f227ed471 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -33,7 +33,7 @@ import type { CacheComponentState, SpawnedCachePool, } from './ReactFiberCacheComponent.new'; -import type {UpdateQueue} from './ReactUpdateQueue.new'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new'; import type {RootState} from './ReactFiberRoot.new'; import { enableSuspenseAvoidThisFallback, @@ -131,7 +131,7 @@ import { cloneUpdateQueue, initializeUpdateQueue, enqueueCapturedUpdate, -} from './ReactUpdateQueue.new'; +} from './ReactFiberClassUpdateQueue.new'; import { NoLane, NoLanes, @@ -234,6 +234,7 @@ import { getWorkInProgressRoot, pushRenderLanes, } from './ReactFiberWorkLoop.new'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new'; import {setWorkInProgressVersion} from './ReactMutableSource.new'; import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent.new'; import {createCapturedValue} from './ReactCapturedValue'; @@ -2626,7 +2627,13 @@ function updateDehydratedSuspenseComponent( suspenseState.retryLane = attemptHydrationAtLane; // TODO: Ideally this would inherit the event time of the current render const eventTime = NoTimestamp; - scheduleUpdateOnFiber(current, attemptHydrationAtLane, eventTime); + enqueueConcurrentRenderForLane(current, attemptHydrationAtLane); + scheduleUpdateOnFiber( + root, + current, + attemptHydrationAtLane, + eventTime, + ); } 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 diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 2303227185..63a8ae2212 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -33,7 +33,7 @@ import type { CacheComponentState, SpawnedCachePool, } from './ReactFiberCacheComponent.old'; -import type {UpdateQueue} from './ReactUpdateQueue.old'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old'; import type {RootState} from './ReactFiberRoot.old'; import { enableSuspenseAvoidThisFallback, @@ -131,7 +131,7 @@ import { cloneUpdateQueue, initializeUpdateQueue, enqueueCapturedUpdate, -} from './ReactUpdateQueue.old'; +} from './ReactFiberClassUpdateQueue.old'; import { NoLane, NoLanes, @@ -234,6 +234,7 @@ import { getWorkInProgressRoot, pushRenderLanes, } from './ReactFiberWorkLoop.old'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.old'; import {setWorkInProgressVersion} from './ReactMutableSource.old'; import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent.old'; import {createCapturedValue} from './ReactCapturedValue'; @@ -2626,7 +2627,13 @@ function updateDehydratedSuspenseComponent( suspenseState.retryLane = attemptHydrationAtLane; // TODO: Ideally this would inherit the event time of the current render const eventTime = NoTimestamp; - scheduleUpdateOnFiber(current, attemptHydrationAtLane, eventTime); + enqueueConcurrentRenderForLane(current, attemptHydrationAtLane); + scheduleUpdateOnFiber( + root, + current, + attemptHydrationAtLane, + eventTime, + ); } 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 diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js index 23bc16ecce..45977bfa6f 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.new.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.new.js @@ -9,7 +9,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.new'; -import type {UpdateQueue} from './ReactUpdateQueue.new'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new'; import type {Flags} from './ReactFiberFlags'; import * as React from 'react'; @@ -58,7 +58,7 @@ import { ForceUpdate, initializeUpdateQueue, cloneUpdateQueue, -} from './ReactUpdateQueue.new'; +} from './ReactFiberClassUpdateQueue.new'; import {NoLanes} from './ReactFiberLane.new'; import { cacheContext, @@ -215,9 +215,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } @@ -250,9 +250,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } @@ -284,9 +284,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.old.js b/packages/react-reconciler/src/ReactFiberClassComponent.old.js index 793156401e..a921cf2f47 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.old.js @@ -9,7 +9,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.old'; -import type {UpdateQueue} from './ReactUpdateQueue.old'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old'; import type {Flags} from './ReactFiberFlags'; import * as React from 'react'; @@ -58,7 +58,7 @@ import { ForceUpdate, initializeUpdateQueue, cloneUpdateQueue, -} from './ReactUpdateQueue.old'; +} from './ReactFiberClassUpdateQueue.old'; import {NoLanes} from './ReactFiberLane.old'; import { cacheContext, @@ -215,9 +215,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } @@ -250,9 +250,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } @@ -284,9 +284,9 @@ const classComponentUpdater = { update.callback = callback; } - enqueueUpdate(fiber, update, lane); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueUpdate(fiber, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitions(root, fiber, lane); } diff --git a/packages/react-reconciler/src/ReactUpdateQueue.new.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js similarity index 94% rename from packages/react-reconciler/src/ReactUpdateQueue.new.js rename to packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js index 1f665a88df..0311920ba2 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.new.js +++ b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.new.js @@ -107,9 +107,12 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags import {StrictLegacyMode} from './ReactTypeOfMode'; import { markSkippedUpdateLanes, - isInterleavedUpdate, + isUnsafeClassRenderPhaseUpdate, } from './ReactFiberWorkLoop.new'; -import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new'; +import { + enqueueConcurrentClassUpdate, + unsafe_markUpdateLaneFromFiberToRoot, +} from './ReactFiberConcurrentUpdates.new'; import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new'; import assign from 'shared/assign'; @@ -129,7 +132,6 @@ export type Update = {| export type SharedQueue = {| pending: Update | null, - interleaved: Update | null, lanes: Lanes, |}; @@ -169,7 +171,6 @@ export function initializeUpdateQueue(fiber: Fiber): void { lastBaseUpdate: null, shared: { pending: null, - interleaved: null, lanes: NoLanes, }, effects: null, @@ -214,40 +215,15 @@ export function enqueueUpdate( fiber: Fiber, update: Update, lane: Lane, -) { +): FiberRoot | null { const updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. - return; + return null; } const sharedQueue: SharedQueue = (updateQueue: any).shared; - if (isInterleavedUpdate(fiber, lane)) { - const interleaved = sharedQueue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushInterleavedQueue(sharedQueue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - sharedQueue.interleaved = update; - } else { - const pending = sharedQueue.pending; - if (pending === null) { - // This is the first update. Create a circular list. - update.next = update; - } else { - update.next = pending.next; - pending.next = update; - } - sharedQueue.pending = update; - } - if (__DEV__) { if ( currentlyProcessingQueue === sharedQueue && @@ -262,6 +238,28 @@ export function enqueueUpdate( didWarnUpdateInsideUpdate = true; } } + + if (isUnsafeClassRenderPhaseUpdate(fiber)) { + // This is an unsafe render phase update. Add directly to the update + // queue so we can process it immediately during the current render. + const pending = sharedQueue.pending; + if (pending === null) { + // This is the first update. Create a circular list. + update.next = update; + } else { + update.next = pending.next; + pending.next = update; + } + sharedQueue.pending = update; + + // Update the childLanes even though we're most likely already rendering + // this fiber. This is for backwards compatibility in the case where you + // update a different component during render phase than the one that is + // currently renderings (a pattern that is accompanied by a warning). + return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); + } else { + return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); + } } export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) { @@ -622,17 +620,7 @@ export function processUpdateQueue( queue.firstBaseUpdate = newFirstBaseUpdate; queue.lastBaseUpdate = newLastBaseUpdate; - // Interleaved updates are stored on a separate queue. We aren't going to - // process them during this render, but we do need to track which lanes - // are remaining. - const lastInterleaved = queue.shared.interleaved; - if (lastInterleaved !== null) { - let interleaved = lastInterleaved; - do { - newLanes = mergeLanes(newLanes, interleaved.lane); - interleaved = ((interleaved: any).next: Update); - } while (interleaved !== lastInterleaved); - } else if (firstBaseUpdate === null) { + if (firstBaseUpdate === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. queue.shared.lanes = NoLanes; diff --git a/packages/react-reconciler/src/ReactUpdateQueue.old.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js similarity index 96% rename from packages/react-reconciler/src/ReactUpdateQueue.old.js rename to packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js index e59ea26f21..cc1643eeef 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.old.js +++ b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.old.js @@ -107,9 +107,12 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags import {StrictLegacyMode} from './ReactTypeOfMode'; import { markSkippedUpdateLanes, - isInterleavedUpdate, + isUnsafeClassRenderPhaseUpdate, } from './ReactFiberWorkLoop.old'; -import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old'; +import { + enqueueConcurrentClassUpdate, + unsafe_markUpdateLaneFromFiberToRoot, +} from './ReactFiberConcurrentUpdates.old'; import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old'; import assign from 'shared/assign'; @@ -214,40 +217,15 @@ export function enqueueUpdate( fiber: Fiber, update: Update, lane: Lane, -) { +): FiberRoot | null { const updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. - return; + return null; } const sharedQueue: SharedQueue = (updateQueue: any).shared; - if (isInterleavedUpdate(fiber, lane)) { - const interleaved = sharedQueue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushInterleavedQueue(sharedQueue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - sharedQueue.interleaved = update; - } else { - const pending = sharedQueue.pending; - if (pending === null) { - // This is the first update. Create a circular list. - update.next = update; - } else { - update.next = pending.next; - pending.next = update; - } - sharedQueue.pending = update; - } - if (__DEV__) { if ( currentlyProcessingQueue === sharedQueue && @@ -262,6 +240,28 @@ export function enqueueUpdate( didWarnUpdateInsideUpdate = true; } } + + if (isUnsafeClassRenderPhaseUpdate(fiber)) { + // This is an unsafe render phase update. Add directly to the update + // queue so we can process it immediately during the current render. + const pending = sharedQueue.pending; + if (pending === null) { + // This is the first update. Create a circular list. + update.next = update; + } else { + update.next = pending.next; + pending.next = update; + } + sharedQueue.pending = update; + + // Update the childLanes even though we're most likely already rendering + // this fiber. This is for backwards compatibility in the case where you + // update a different component during render phase than the one that is + // currently renderings (a pattern that is accompanied by a warning). + return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); + } else { + return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); + } } export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 7e8459d496..d023c1f776 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -19,7 +19,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.new'; import type {SuspenseState} from './ReactFiberSuspenseComponent.new'; -import type {UpdateQueue} from './ReactUpdateQueue.new'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new'; import type {Wakeable} from 'shared/ReactTypes'; import type { @@ -100,7 +100,7 @@ import { startPassiveEffectTimer, } from './ReactProfilerTimer.new'; import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode'; -import {commitUpdateQueue} from './ReactUpdateQueue.new'; +import {commitUpdateQueue} from './ReactFiberClassUpdateQueue.new'; import { getPublicInstance, supportsMutation, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 4dceedd3a3..a9eae21939 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -19,7 +19,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane.old'; import type {SuspenseState} from './ReactFiberSuspenseComponent.old'; -import type {UpdateQueue} from './ReactUpdateQueue.old'; +import type {UpdateQueue} from './ReactFiberClassUpdateQueue.old'; import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old'; import type {Wakeable} from 'shared/ReactTypes'; import type { @@ -100,7 +100,7 @@ import { startPassiveEffectTimer, } from './ReactProfilerTimer.old'; import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode'; -import {commitUpdateQueue} from './ReactUpdateQueue.old'; +import {commitUpdateQueue} from './ReactFiberClassUpdateQueue.old'; import { getPublicInstance, supportsMutation, diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js new file mode 100644 index 0000000000..ed5a02cfea --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.new.js @@ -0,0 +1,206 @@ +/** + * 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 {FiberRoot} from './ReactInternalTypes'; +import type { + UpdateQueue as HookQueue, + Update as HookUpdate, +} from './ReactFiberHooks.new'; +import type { + SharedQueue as ClassQueue, + Update as ClassUpdate, +} from './ReactFiberClassUpdateQueue.new'; +import type {Lane, Lanes} from './ReactFiberLane.new'; + +import {warnAboutUpdateOnNotYetMountedFiberInDEV} from './ReactFiberWorkLoop.new'; +import {NoLane, NoLanes, mergeLanes} from './ReactFiberLane.new'; +import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; +import {HostRoot} from './ReactWorkTags'; + +type ConcurrentUpdate = { + next: ConcurrentUpdate, +}; + +type ConcurrentQueue = { + pending: ConcurrentUpdate | null, +}; + +// If a render is in progress, and we receive an update from a concurrent event, +// we wait until the current render is over (either finished or interrupted) +// before adding it to the fiber/hook queue. Push to this array so we can +// access the queue, fiber, update, et al later. +const concurrentQueues: Array = []; +let concurrentQueuesIndex = 0; + +export function finishQueueingConcurrentUpdates(): Lanes { + const endIndex = concurrentQueuesIndex; + concurrentQueuesIndex = 0; + + let lanes = NoLanes; + + let i = 0; + while (i < endIndex) { + const fiber: Fiber = concurrentQueues[i]; + concurrentQueues[i++] = null; + const queue: ConcurrentQueue = concurrentQueues[i]; + concurrentQueues[i++] = null; + const update: ConcurrentUpdate = concurrentQueues[i]; + concurrentQueues[i++] = null; + const lane: Lane = concurrentQueues[i]; + concurrentQueues[i++] = null; + + if (queue !== null && update !== null) { + const pending = queue.pending; + if (pending === null) { + // This is the first update. Create a circular list. + update.next = update; + } else { + update.next = pending.next; + pending.next = update; + } + queue.pending = update; + } + + if (lane !== NoLane) { + lanes = mergeLanes(lanes, lane); + markUpdateLaneFromFiberToRoot(fiber, lane); + } + } + + return lanes; +} + +function enqueueUpdate( + fiber: Fiber, + queue: ConcurrentQueue | null, + update: ConcurrentUpdate | null, + lane: Lane, +) { + // Don't update the `childLanes` on the return path yet. If we already in + // the middle of rendering, wait until after it has completed. + concurrentQueues[concurrentQueuesIndex++] = fiber; + concurrentQueues[concurrentQueuesIndex++] = queue; + concurrentQueues[concurrentQueuesIndex++] = update; + concurrentQueues[concurrentQueuesIndex++] = lane; + + // The fiber's `lane` field is used in some places to check if any work is + // scheduled, to perform an eager bailout, so we need to update it immediately. + // TODO: We should probably move this to the "shared" queue instead. + fiber.lanes = mergeLanes(fiber.lanes, lane); + const alternate = fiber.alternate; + if (alternate !== null) { + alternate.lanes = mergeLanes(alternate.lanes, lane); + } +} + +export function enqueueConcurrentHookUpdate( + fiber: Fiber, + queue: HookQueue, + update: HookUpdate, + lane: Lane, +): FiberRoot | null { + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); + return getRootForUpdatedFiber(fiber); +} + +export function enqueueConcurrentHookUpdateAndEagerlyBailout( + fiber: Fiber, + queue: HookQueue, + update: HookUpdate, +): void { + // This function is used to queue an update that doesn't need a rerender. The + // only reason we queue it is in case there's a subsequent higher priority + // update that causes it to be rebased. + const lane = NoLane; + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); +} + +export function enqueueConcurrentClassUpdate( + fiber: Fiber, + queue: ClassQueue, + update: ClassUpdate, + lane: Lane, +): FiberRoot | null { + const concurrentQueue: ConcurrentQueue = (queue: any); + const concurrentUpdate: ConcurrentUpdate = (update: any); + enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane); + return getRootForUpdatedFiber(fiber); +} + +export function enqueueConcurrentRenderForLane( + fiber: Fiber, + lane: Lane, +): FiberRoot | null { + enqueueUpdate(fiber, null, null, lane); + return getRootForUpdatedFiber(fiber); +} + +// Calling this function outside this module should only be done for backwards +// compatibility and should always be accompanied by a warning. +export function unsafe_markUpdateLaneFromFiberToRoot( + sourceFiber: Fiber, + lane: Lane, +): FiberRoot | null { + markUpdateLaneFromFiberToRoot(sourceFiber, lane); + return getRootForUpdatedFiber(sourceFiber); +} + +function markUpdateLaneFromFiberToRoot(sourceFiber: Fiber, lane: Lane): void { + // Update the source fiber's lanes + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; + if (alternate !== null) { + alternate.lanes = mergeLanes(alternate.lanes, lane); + } + // Walk the parent path to the root and update the child lanes. + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } + parent = parent.return; + } +} + +function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null { + // When a setState happens, we must ensure the root is scheduled. Because + // update queues do not have a backpointer to the root, the only way to do + // this currently is to walk up the return path. This used to not be a big + // deal because we would have to walk up the return path to set + // the `childLanes`, anyway, but now those two traversals happen at + // different times. + // TODO: Consider adding a `root` backpointer on the update queue. + detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber); + let node = sourceFiber; + let parent = node.return; + while (parent !== null) { + detectUpdateOnUnmountedFiber(sourceFiber, node); + node = parent; + parent = node.return; + } + return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null; +} + +function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) { + if (__DEV__) { + const alternate = parent.alternate; + if ( + alternate === null && + (parent.flags & (Placement | Hydrating)) !== NoFlags + ) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); + } + } +} diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js new file mode 100644 index 0000000000..8f3a11c938 --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.old.js @@ -0,0 +1,184 @@ +/** + * 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 {FiberRoot} from './ReactInternalTypes'; +import type { + UpdateQueue as HookQueue, + Update as HookUpdate, +} from './ReactFiberHooks.old'; +import type { + SharedQueue as ClassQueue, + Update as ClassUpdate, +} from './ReactFiberClassUpdateQueue.old'; +import type {Lane} from './ReactFiberLane.old'; + +import {warnAboutUpdateOnNotYetMountedFiberInDEV} from './ReactFiberWorkLoop.old'; +import {mergeLanes} from './ReactFiberLane.old'; +import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; +import {HostRoot} from './ReactWorkTags'; + +// An array of all update queues that received updates during the current +// render. When this render exits, either because it finishes or because it is +// interrupted, the interleaved updates will be transferred onto the main part +// of the queue. +let concurrentQueues: Array< + HookQueue | ClassQueue, +> | null = null; + +export function pushConcurrentUpdateQueue( + queue: HookQueue | ClassQueue, +) { + if (concurrentQueues === null) { + concurrentQueues = [queue]; + } else { + concurrentQueues.push(queue); + } +} + +export function finishQueueingConcurrentUpdates() { + // Transfer the interleaved updates onto the main queue. Each queue has a + // `pending` field and an `interleaved` field. When they are not null, they + // point to the last node in a circular linked list. We need to append the + // interleaved list to the end of the pending list by joining them into a + // single, circular list. + if (concurrentQueues !== null) { + for (let i = 0; i < concurrentQueues.length; i++) { + const queue = concurrentQueues[i]; + const lastInterleavedUpdate = queue.interleaved; + if (lastInterleavedUpdate !== null) { + queue.interleaved = null; + const firstInterleavedUpdate = lastInterleavedUpdate.next; + const lastPendingUpdate = queue.pending; + if (lastPendingUpdate !== null) { + const firstPendingUpdate = lastPendingUpdate.next; + lastPendingUpdate.next = (firstInterleavedUpdate: any); + lastInterleavedUpdate.next = (firstPendingUpdate: any); + } + queue.pending = (lastInterleavedUpdate: any); + } + } + concurrentQueues = null; + } +} + +export function enqueueConcurrentHookUpdate( + fiber: Fiber, + queue: HookQueue, + update: HookUpdate, + lane: Lane, +) { + const interleaved = queue.interleaved; + if (interleaved === null) { + // This is the first update. Create a circular list. + update.next = update; + // At the end of the current render, this queue's interleaved updates will + // be transferred to the pending queue. + pushConcurrentUpdateQueue(queue); + } else { + update.next = interleaved.next; + interleaved.next = update; + } + queue.interleaved = update; + + return markUpdateLaneFromFiberToRoot(fiber, lane); +} + +export function enqueueConcurrentHookUpdateAndEagerlyBailout( + fiber: Fiber, + queue: HookQueue, + update: HookUpdate, + lane: Lane, +): void { + const interleaved = queue.interleaved; + if (interleaved === null) { + // This is the first update. Create a circular list. + update.next = update; + // At the end of the current render, this queue's interleaved updates will + // be transferred to the pending queue. + pushConcurrentUpdateQueue(queue); + } else { + update.next = interleaved.next; + interleaved.next = update; + } + queue.interleaved = update; +} + +export function enqueueConcurrentClassUpdate( + fiber: Fiber, + queue: ClassQueue, + update: ClassUpdate, + lane: Lane, +) { + const interleaved = queue.interleaved; + if (interleaved === null) { + // This is the first update. Create a circular list. + update.next = update; + // At the end of the current render, this queue's interleaved updates will + // be transferred to the pending queue. + pushConcurrentUpdateQueue(queue); + } else { + update.next = interleaved.next; + interleaved.next = update; + } + queue.interleaved = update; + + return markUpdateLaneFromFiberToRoot(fiber, lane); +} + +export function enqueueConcurrentRenderForLane(fiber: Fiber, lane: Lane) { + return markUpdateLaneFromFiberToRoot(fiber, lane); +} + +// Calling this function outside this module should only be done for backwards +// compatibility and should always be accompanied by a warning. +export const unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot; + +function markUpdateLaneFromFiberToRoot( + sourceFiber: Fiber, + lane: Lane, +): FiberRoot | null { + // Update the source fiber's lanes + sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); + let alternate = sourceFiber.alternate; + if (alternate !== null) { + alternate.lanes = mergeLanes(alternate.lanes, lane); + } + if (__DEV__) { + if ( + alternate === null && + (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags + ) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); + } + } + // Walk the parent path to the root and update the child lanes. + let node = sourceFiber; + let parent = sourceFiber.return; + while (parent !== null) { + parent.childLanes = mergeLanes(parent.childLanes, lane); + alternate = parent.alternate; + if (alternate !== null) { + alternate.childLanes = mergeLanes(alternate.childLanes, lane); + } else { + if (__DEV__) { + if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { + warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); + } + } + } + node = parent; + parent = parent.return; + } + if (node.tag === HostRoot) { + const root: FiberRoot = node.stateNode; + return root; + } else { + return null; + } +} diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 1742d00ae1..697e87dd01 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -87,7 +87,6 @@ import { requestUpdateLane, requestEventTime, markSkippedUpdateLanes, - isInterleavedUpdate, } from './ReactFiberWorkLoop.new'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; @@ -111,14 +110,18 @@ import { createUpdate as createLegacyQueueUpdate, enqueueUpdate as enqueueLegacyQueueUpdate, entangleTransitions as entangleLegacyQueueTransitions, -} from './ReactUpdateQueue.new'; -import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new'; +} from './ReactFiberClassUpdateQueue.new'; +import { + enqueueConcurrentHookUpdate, + enqueueConcurrentHookUpdateAndEagerlyBailout, + enqueueConcurrentRenderForLane, +} from './ReactFiberConcurrentUpdates.new'; import {getTreeId} from './ReactFiberTreeContext.new'; import {now} from './Scheduler'; const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals; -type Update = {| +export type Update = {| lane: Lane, action: A, hasEagerState: boolean, @@ -128,7 +131,6 @@ type Update = {| export type UpdateQueue = {| pending: Update | null, - interleaved: Update | null, lanes: Lanes, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, @@ -738,7 +740,6 @@ function mountReducer( hook.memoizedState = hook.baseState = initialState; const queue: UpdateQueue = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: reducer, @@ -885,22 +886,7 @@ function updateReducer( queue.lastRenderedState = newState; } - // Interleaved updates are stored on a separate queue. We aren't going to - // process them during this render, but we do need to track which lanes - // are remaining. - const lastInterleaved = queue.interleaved; - if (lastInterleaved !== null) { - let interleaved = lastInterleaved; - do { - const interleavedLane = interleaved.lane; - currentlyRenderingFiber.lanes = mergeLanes( - currentlyRenderingFiber.lanes, - interleavedLane, - ); - markSkippedUpdateLanes(interleavedLane); - interleaved = ((interleaved: any).next: Update); - } while (interleaved !== lastInterleaved); - } else if (baseQueue === null) { + if (baseQueue === null) { // `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty. queue.lanes = NoLanes; @@ -1208,7 +1194,6 @@ function useMutableSource( // including any interleaving updates that occur. const newQueue: UpdateQueue> = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, @@ -1497,7 +1482,10 @@ function checkIfSnapshotChanged(inst) { } function forceStoreRerender(fiber) { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } function mountState( @@ -1511,7 +1499,6 @@ function mountState( hook.memoizedState = hook.baseState = initialState; const queue: UpdateQueue> = { pending: null, - interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, @@ -2153,10 +2140,13 @@ function refreshCache(fiber: Fiber, seedKey: ?() => T, seedValue: T) { switch (provider.tag) { case CacheComponent: case HostRoot: { + // Schedule an update on the cache boundary to trigger a refresh. const lane = requestUpdateLane(provider); const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(provider, lane, eventTime); + const refreshUpdate = createLegacyQueueUpdate(eventTime, lane); + const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane); if (root !== null) { + scheduleUpdateOnFiber(root, provider, lane, eventTime); entangleLegacyQueueTransitions(root, provider, lane); } @@ -2170,13 +2160,10 @@ function refreshCache(fiber: Fiber, seedKey: ?() => T, seedValue: T) { seededCache.data.set(seedKey, seedValue); } - // Schedule an update on the cache boundary to trigger a refresh. - const refreshUpdate = createLegacyQueueUpdate(eventTime, lane); const payload = { cache: seededCache, }; refreshUpdate.payload = payload; - enqueueLegacyQueueUpdate(provider, refreshUpdate, lane); return; } } @@ -2213,10 +2200,10 @@ function dispatchReducerAction( if (isRenderPhaseUpdate(fiber)) { enqueueRenderPhaseUpdate(queue, update); } else { - enqueueUpdate(fiber, queue, update, lane); - const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitionUpdate(root, queue, lane); } } @@ -2252,8 +2239,6 @@ function dispatchSetState( if (isRenderPhaseUpdate(fiber)) { enqueueRenderPhaseUpdate(queue, update); } else { - enqueueUpdate(fiber, queue, update, lane); - const alternate = fiber.alternate; if ( fiber.lanes === NoLanes && @@ -2283,6 +2268,8 @@ function dispatchSetState( // It's still possible that we'll need to rebase this update later, // if the component re-renders for a different reason and by that // time the reducer has changed. + // TODO: Do we still need to entangle transitions in this case? + enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update); return; } } catch (error) { @@ -2294,9 +2281,11 @@ function dispatchSetState( } } } - const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + + const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitionUpdate(root, queue, lane); } } @@ -2331,38 +2320,7 @@ function enqueueRenderPhaseUpdate( queue.pending = update; } -function enqueueUpdate( - fiber: Fiber, - queue: UpdateQueue, - update: Update, - lane: Lane, -) { - if (isInterleavedUpdate(fiber, lane)) { - const interleaved = queue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushInterleavedQueue(queue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - queue.interleaved = update; - } else { - const pending = queue.pending; - if (pending === null) { - // This is the first update. Create a circular list. - update.next = update; - } else { - update.next = pending.next; - pending.next = update; - } - queue.pending = update; - } -} - +// TODO: Move to ReactFiberConcurrentUpdates? function entangleTransitionUpdate( root: FiberRoot, queue: UpdateQueue, diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 20717bde1c..fc25083fe4 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -87,7 +87,6 @@ import { requestUpdateLane, requestEventTime, markSkippedUpdateLanes, - isInterleavedUpdate, } from './ReactFiberWorkLoop.old'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; @@ -111,14 +110,18 @@ import { createUpdate as createLegacyQueueUpdate, enqueueUpdate as enqueueLegacyQueueUpdate, entangleTransitions as entangleLegacyQueueTransitions, -} from './ReactUpdateQueue.old'; -import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old'; +} from './ReactFiberClassUpdateQueue.old'; +import { + enqueueConcurrentHookUpdate, + enqueueConcurrentHookUpdateAndEagerlyBailout, + enqueueConcurrentRenderForLane, +} from './ReactFiberConcurrentUpdates.old'; import {getTreeId} from './ReactFiberTreeContext.old'; import {now} from './Scheduler'; const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals; -type Update = {| +export type Update = {| lane: Lane, action: A, hasEagerState: boolean, @@ -1497,7 +1500,10 @@ function checkIfSnapshotChanged(inst) { } function forceStoreRerender(fiber) { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } function mountState( @@ -2153,10 +2159,13 @@ function refreshCache(fiber: Fiber, seedKey: ?() => T, seedValue: T) { switch (provider.tag) { case CacheComponent: case HostRoot: { + // Schedule an update on the cache boundary to trigger a refresh. const lane = requestUpdateLane(provider); const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(provider, lane, eventTime); + const refreshUpdate = createLegacyQueueUpdate(eventTime, lane); + const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane); if (root !== null) { + scheduleUpdateOnFiber(root, provider, lane, eventTime); entangleLegacyQueueTransitions(root, provider, lane); } @@ -2170,13 +2179,10 @@ function refreshCache(fiber: Fiber, seedKey: ?() => T, seedValue: T) { seededCache.data.set(seedKey, seedValue); } - // Schedule an update on the cache boundary to trigger a refresh. - const refreshUpdate = createLegacyQueueUpdate(eventTime, lane); const payload = { cache: seededCache, }; refreshUpdate.payload = payload; - enqueueLegacyQueueUpdate(provider, refreshUpdate, lane); return; } } @@ -2213,10 +2219,10 @@ function dispatchReducerAction( if (isRenderPhaseUpdate(fiber)) { enqueueRenderPhaseUpdate(queue, update); } else { - enqueueUpdate(fiber, queue, update, lane); - const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitionUpdate(root, queue, lane); } } @@ -2252,8 +2258,6 @@ function dispatchSetState( if (isRenderPhaseUpdate(fiber)) { enqueueRenderPhaseUpdate(queue, update); } else { - enqueueUpdate(fiber, queue, update, lane); - const alternate = fiber.alternate; if ( fiber.lanes === NoLanes && @@ -2283,6 +2287,13 @@ function dispatchSetState( // It's still possible that we'll need to rebase this update later, // if the component re-renders for a different reason and by that // time the reducer has changed. + // TODO: Do we still need to entangle transitions in this case? + enqueueConcurrentHookUpdateAndEagerlyBailout( + fiber, + queue, + update, + lane, + ); return; } } catch (error) { @@ -2294,9 +2305,11 @@ function dispatchSetState( } } } - const eventTime = requestEventTime(); - const root = scheduleUpdateOnFiber(fiber, lane, eventTime); + + const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); entangleTransitionUpdate(root, queue, lane); } } @@ -2331,38 +2344,7 @@ function enqueueRenderPhaseUpdate( queue.pending = update; } -function enqueueUpdate( - fiber: Fiber, - queue: UpdateQueue, - update: Update, - lane: Lane, -) { - if (isInterleavedUpdate(fiber, lane)) { - const interleaved = queue.interleaved; - if (interleaved === null) { - // This is the first update. Create a circular list. - update.next = update; - // At the end of the current render, this queue's interleaved updates will - // be transferred to the pending queue. - pushInterleavedQueue(queue); - } else { - update.next = interleaved.next; - interleaved.next = update; - } - queue.interleaved = update; - } else { - const pending = queue.pending; - if (pending === null) { - // This is the first update. Create a circular list. - update.next = update; - } else { - update.next = pending.next; - pending.next = update; - } - queue.pending = update; - } -} - +// TODO: Move to ReactFiberConcurrentUpdates? function entangleTransitionUpdate( root: FiberRoot, queue: UpdateQueue, diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.new.js b/packages/react-reconciler/src/ReactFiberHotReloading.new.js index 0867ffeb78..22b78280f9 100644 --- a/packages/react-reconciler/src/ReactFiberHotReloading.new.js +++ b/packages/react-reconciler/src/ReactFiberHotReloading.new.js @@ -20,6 +20,7 @@ import { scheduleUpdateOnFiber, flushPassiveEffects, } from './ReactFiberWorkLoop.new'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new'; import {updateContainer} from './ReactFiberReconciler.new'; import {emptyContextObject} from './ReactFiberContext.new'; import {SyncLane, NoTimestamp} from './ReactFiberLane.new'; @@ -321,7 +322,10 @@ function scheduleFibersWithFamiliesRecursively( fiber._debugNeedsRemount = true; } if (needsRemount || needsRender) { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } if (child !== null && !needsRemount) { scheduleFibersWithFamiliesRecursively( diff --git a/packages/react-reconciler/src/ReactFiberHotReloading.old.js b/packages/react-reconciler/src/ReactFiberHotReloading.old.js index 4a5f53d3be..805176560c 100644 --- a/packages/react-reconciler/src/ReactFiberHotReloading.old.js +++ b/packages/react-reconciler/src/ReactFiberHotReloading.old.js @@ -20,6 +20,7 @@ import { scheduleUpdateOnFiber, flushPassiveEffects, } from './ReactFiberWorkLoop.old'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.old'; import {updateContainer} from './ReactFiberReconciler.old'; import {emptyContextObject} from './ReactFiberContext.old'; import {SyncLane, NoTimestamp} from './ReactFiberLane.old'; @@ -321,7 +322,10 @@ function scheduleFibersWithFamiliesRecursively( fiber._debugNeedsRemount = true; } if (needsRemount || needsRender) { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } if (child !== null && !needsRemount) { scheduleFibersWithFamiliesRecursively( diff --git a/packages/react-reconciler/src/ReactFiberInterleavedUpdates.new.js b/packages/react-reconciler/src/ReactFiberInterleavedUpdates.new.js deleted file mode 100644 index 010730b1e7..0000000000 --- a/packages/react-reconciler/src/ReactFiberInterleavedUpdates.new.js +++ /dev/null @@ -1,59 +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 {UpdateQueue as HookQueue} from './ReactFiberHooks.new'; -import type {SharedQueue as ClassQueue} from './ReactUpdateQueue.new'; - -// An array of all update queues that received updates during the current -// render. When this render exits, either because it finishes or because it is -// interrupted, the interleaved updates will be transferred onto the main part -// of the queue. -let interleavedQueues: Array< - HookQueue | ClassQueue, -> | null = null; - -export function pushInterleavedQueue( - queue: HookQueue | ClassQueue, -) { - if (interleavedQueues === null) { - interleavedQueues = [queue]; - } else { - interleavedQueues.push(queue); - } -} - -export function hasInterleavedUpdates() { - return interleavedQueues !== null; -} - -export function enqueueInterleavedUpdates() { - // Transfer the interleaved updates onto the main queue. Each queue has a - // `pending` field and an `interleaved` field. When they are not null, they - // point to the last node in a circular linked list. We need to append the - // interleaved list to the end of the pending list by joining them into a - // single, circular list. - if (interleavedQueues !== null) { - for (let i = 0; i < interleavedQueues.length; i++) { - const queue = interleavedQueues[i]; - const lastInterleavedUpdate = queue.interleaved; - if (lastInterleavedUpdate !== null) { - queue.interleaved = null; - const firstInterleavedUpdate = lastInterleavedUpdate.next; - const lastPendingUpdate = queue.pending; - if (lastPendingUpdate !== null) { - const firstPendingUpdate = lastPendingUpdate.next; - lastPendingUpdate.next = (firstInterleavedUpdate: any); - lastInterleavedUpdate.next = (firstPendingUpdate: any); - } - queue.pending = (lastInterleavedUpdate: any); - } - } - interleavedQueues = null; - } -} diff --git a/packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js b/packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js deleted file mode 100644 index 0d3319801d..0000000000 --- a/packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js +++ /dev/null @@ -1,59 +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 {UpdateQueue as HookQueue} from './ReactFiberHooks.old'; -import type {SharedQueue as ClassQueue} from './ReactUpdateQueue.old'; - -// An array of all update queues that received updates during the current -// render. When this render exits, either because it finishes or because it is -// interrupted, the interleaved updates will be transferred onto the main part -// of the queue. -let interleavedQueues: Array< - HookQueue | ClassQueue, -> | null = null; - -export function pushInterleavedQueue( - queue: HookQueue | ClassQueue, -) { - if (interleavedQueues === null) { - interleavedQueues = [queue]; - } else { - interleavedQueues.push(queue); - } -} - -export function hasInterleavedUpdates() { - return interleavedQueues !== null; -} - -export function enqueueInterleavedUpdates() { - // Transfer the interleaved updates onto the main queue. Each queue has a - // `pending` field and an `interleaved` field. When they are not null, they - // point to the last node in a circular linked list. We need to append the - // interleaved list to the end of the pending list by joining them into a - // single, circular list. - if (interleavedQueues !== null) { - for (let i = 0; i < interleavedQueues.length; i++) { - const queue = interleavedQueues[i]; - const lastInterleavedUpdate = queue.interleaved; - if (lastInterleavedUpdate !== null) { - queue.interleaved = null; - const firstInterleavedUpdate = lastInterleavedUpdate.next; - const lastPendingUpdate = queue.pending; - if (lastPendingUpdate !== null) { - const firstPendingUpdate = lastPendingUpdate.next; - lastPendingUpdate.next = (firstInterleavedUpdate: any); - lastInterleavedUpdate.next = (firstPendingUpdate: any); - } - queue.pending = (lastInterleavedUpdate: any); - } - } - interleavedQueues = null; - } -} diff --git a/packages/react-reconciler/src/ReactFiberNewContext.new.js b/packages/react-reconciler/src/ReactFiberNewContext.new.js index 68862e5688..eae7b232c5 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.new.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.new.js @@ -15,7 +15,7 @@ import type { } from './ReactInternalTypes'; import type {StackCursor} from './ReactFiberStack.new'; import type {Lanes} from './ReactFiberLane.new'; -import type {SharedQueue} from './ReactUpdateQueue.new'; +import type {SharedQueue} from './ReactFiberClassUpdateQueue.new'; import {isPrimaryRenderer} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack.new'; @@ -39,7 +39,7 @@ import { } from './ReactFiberFlags'; import is from 'shared/objectIs'; -import {createUpdate, ForceUpdate} from './ReactUpdateQueue.new'; +import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue.new'; import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new'; import { enableLazyContextPropagation, diff --git a/packages/react-reconciler/src/ReactFiberNewContext.old.js b/packages/react-reconciler/src/ReactFiberNewContext.old.js index de96664e77..cbc2d0069b 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.old.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.old.js @@ -15,7 +15,7 @@ import type { } from './ReactInternalTypes'; import type {StackCursor} from './ReactFiberStack.old'; import type {Lanes} from './ReactFiberLane.old'; -import type {SharedQueue} from './ReactUpdateQueue.old'; +import type {SharedQueue} from './ReactFiberClassUpdateQueue.old'; import {isPrimaryRenderer} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack.old'; @@ -39,7 +39,7 @@ import { } from './ReactFiberFlags'; import is from 'shared/objectIs'; -import {createUpdate, ForceUpdate} from './ReactUpdateQueue.old'; +import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue.old'; import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old'; import { enableLazyContextPropagation, diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index 136276637d..f9899c93b9 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -68,11 +68,12 @@ import { discreteUpdates, flushPassiveEffects, } from './ReactFiberWorkLoop.new'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new'; import { createUpdate, enqueueUpdate, entangleTransitions, -} from './ReactUpdateQueue.new'; +} from './ReactFiberClassUpdateQueue.new'; import { isRendering as ReactCurrentFiberIsRendering, current as ReactCurrentFiberCurrent, @@ -377,9 +378,9 @@ export function updateContainer( update.callback = callback; } - enqueueUpdate(current, update, lane); - const root = scheduleUpdateOnFiber(current, lane, eventTime); + const root = enqueueUpdate(current, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, current, lane, eventTime); entangleTransitions(root, current, lane); } @@ -413,7 +414,7 @@ export function getPublicRootInstance( export function attemptSynchronousHydration(fiber: Fiber): void { switch (fiber.tag) { - case HostRoot: + case HostRoot: { const root: FiberRoot = fiber.stateNode; if (isRootDehydrated(root)) { // Flush the first scheduled "update". @@ -421,15 +422,22 @@ export function attemptSynchronousHydration(fiber: Fiber): void { flushRoot(root, lanes); } break; - case SuspenseComponent: - const eventTime = requestEventTime(); - flushSync(() => scheduleUpdateOnFiber(fiber, SyncLane, eventTime)); + } + case SuspenseComponent: { + flushSync(() => { + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime); + } + }); // 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 retryLane = SyncLane; markRetryLaneIfNotHydrated(fiber, retryLane); break; + } } } @@ -460,9 +468,12 @@ export function attemptDiscreteHydration(fiber: Fiber): void { // Suspense. return; } - const eventTime = requestEventTime(); const lane = SyncLane; - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -474,9 +485,12 @@ export function attemptContinuousHydration(fiber: Fiber): void { // Suspense. return; } - const eventTime = requestEventTime(); const lane = SelectiveHydrationLane; - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -486,9 +500,12 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void { // their priority other than synchronously flush it. return; } - const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -664,7 +681,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; overrideHookStateDeletePath = ( @@ -685,7 +705,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; overrideHookStateRenamePath = ( @@ -707,7 +730,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; @@ -717,14 +743,20 @@ if (__DEV__) { if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; overridePropsDeletePath = (fiber: Fiber, path: Array) => { fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path); if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; overridePropsRenamePath = ( fiber: Fiber, @@ -735,11 +767,17 @@ if (__DEV__) { if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; scheduleUpdate = (fiber: Fiber) => { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => { diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index e014519320..58ee3656d5 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -68,11 +68,12 @@ import { discreteUpdates, flushPassiveEffects, } from './ReactFiberWorkLoop.old'; +import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.old'; import { createUpdate, enqueueUpdate, entangleTransitions, -} from './ReactUpdateQueue.old'; +} from './ReactFiberClassUpdateQueue.old'; import { isRendering as ReactCurrentFiberIsRendering, current as ReactCurrentFiberCurrent, @@ -377,9 +378,9 @@ export function updateContainer( update.callback = callback; } - enqueueUpdate(current, update, lane); - const root = scheduleUpdateOnFiber(current, lane, eventTime); + const root = enqueueUpdate(current, update, lane); if (root !== null) { + scheduleUpdateOnFiber(root, current, lane, eventTime); entangleTransitions(root, current, lane); } @@ -413,7 +414,7 @@ export function getPublicRootInstance( export function attemptSynchronousHydration(fiber: Fiber): void { switch (fiber.tag) { - case HostRoot: + case HostRoot: { const root: FiberRoot = fiber.stateNode; if (isRootDehydrated(root)) { // Flush the first scheduled "update". @@ -421,15 +422,22 @@ export function attemptSynchronousHydration(fiber: Fiber): void { flushRoot(root, lanes); } break; - case SuspenseComponent: - const eventTime = requestEventTime(); - flushSync(() => scheduleUpdateOnFiber(fiber, SyncLane, eventTime)); + } + case SuspenseComponent: { + flushSync(() => { + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime); + } + }); // 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 retryLane = SyncLane; markRetryLaneIfNotHydrated(fiber, retryLane); break; + } } } @@ -460,9 +468,12 @@ export function attemptDiscreteHydration(fiber: Fiber): void { // Suspense. return; } - const eventTime = requestEventTime(); const lane = SyncLane; - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -474,9 +485,12 @@ export function attemptContinuousHydration(fiber: Fiber): void { // Suspense. return; } - const eventTime = requestEventTime(); const lane = SelectiveHydrationLane; - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -486,9 +500,12 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void { // their priority other than synchronously flush it. return; } - const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); - scheduleUpdateOnFiber(fiber, lane, eventTime); + const root = enqueueConcurrentRenderForLane(fiber, lane); + if (root !== null) { + const eventTime = requestEventTime(); + scheduleUpdateOnFiber(root, fiber, lane, eventTime); + } markRetryLaneIfNotHydrated(fiber, lane); } @@ -664,7 +681,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; overrideHookStateDeletePath = ( @@ -685,7 +705,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; overrideHookStateRenamePath = ( @@ -707,7 +730,10 @@ if (__DEV__) { // Shallow cloning props works as a workaround for now to bypass the bailout check. fiber.memoizedProps = {...fiber.memoizedProps}; - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } } }; @@ -717,14 +743,20 @@ if (__DEV__) { if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; overridePropsDeletePath = (fiber: Fiber, path: Array) => { fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path); if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; overridePropsRenamePath = ( fiber: Fiber, @@ -735,11 +767,17 @@ if (__DEV__) { if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; scheduleUpdate = (fiber: Fiber) => { - scheduleUpdateOnFiber(fiber, SyncLane, NoTimestamp); + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + } }; setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => { diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 304073ed01..ca9edcc3de 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -37,7 +37,7 @@ import { enableUpdaterTracking, enableTransitionTracing, } from 'shared/ReactFeatureFlags'; -import {initializeUpdateQueue} from './ReactUpdateQueue.new'; +import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue.new'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; import {createCache, retainCache} from './ReactFiberCacheComponent.new'; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 3c4086394a..f9c9e8091c 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -37,7 +37,7 @@ import { enableUpdaterTracking, enableTransitionTracing, } from 'shared/ReactFeatureFlags'; -import {initializeUpdateQueue} from './ReactUpdateQueue.old'; +import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue.old'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; import {createCache, retainCache} from './ReactFiberCacheComponent.old'; diff --git a/packages/react-reconciler/src/ReactFiberThrow.new.js b/packages/react-reconciler/src/ReactFiberThrow.new.js index 9a3d1b6192..3d13cd6407 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.new.js +++ b/packages/react-reconciler/src/ReactFiberThrow.new.js @@ -11,7 +11,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {Lane, Lanes} from './ReactFiberLane.new'; import type {CapturedValue} from './ReactCapturedValue'; -import type {Update} from './ReactUpdateQueue.new'; +import type {Update} from './ReactFiberClassUpdateQueue.new'; import type {Wakeable} from 'shared/ReactTypes'; import type {SuspenseContext} from './ReactFiberSuspenseContext.new'; @@ -48,7 +48,7 @@ import { CaptureUpdate, ForceUpdate, enqueueUpdate, -} from './ReactUpdateQueue.new'; +} from './ReactFiberClassUpdateQueue.new'; import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new'; import { suspenseStackCursor, diff --git a/packages/react-reconciler/src/ReactFiberThrow.old.js b/packages/react-reconciler/src/ReactFiberThrow.old.js index 66f91a69d3..ba0dfb5c32 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.old.js +++ b/packages/react-reconciler/src/ReactFiberThrow.old.js @@ -11,7 +11,7 @@ import type {Fiber} from './ReactInternalTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {Lane, Lanes} from './ReactFiberLane.old'; import type {CapturedValue} from './ReactCapturedValue'; -import type {Update} from './ReactUpdateQueue.old'; +import type {Update} from './ReactFiberClassUpdateQueue.old'; import type {Wakeable} from 'shared/ReactTypes'; import type {SuspenseContext} from './ReactFiberSuspenseContext.old'; @@ -48,7 +48,7 @@ import { CaptureUpdate, ForceUpdate, enqueueUpdate, -} from './ReactUpdateQueue.old'; +} from './ReactFiberClassUpdateQueue.old'; import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.old'; import { suspenseStackCursor, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 6e438aaa7b..9857207dfb 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -105,11 +105,9 @@ import { import {LegacyRoot} from './ReactRootTags'; import { NoFlags, - Placement, Incomplete, StoreConsistency, HostEffectMask, - Hydrating, ForceClientRender, BeforeMutationMask, MutationMask, @@ -182,7 +180,7 @@ import { invokePassiveEffectUnmountInDEV, reportUncaughtErrorInDEV, } from './ReactFiberCommitWork.new'; -import {enqueueUpdate} from './ReactUpdateQueue.new'; +import {enqueueUpdate} from './ReactFiberClassUpdateQueue.new'; import {resetContextDependencies} from './ReactFiberNewContext.new'; import { resetHooksAfterThrow, @@ -196,9 +194,9 @@ import { createCursor, } from './ReactFiberStack.new'; import { - enqueueInterleavedUpdates, - hasInterleavedUpdates, -} from './ReactFiberInterleavedUpdates.new'; + enqueueConcurrentRenderForLane, + finishQueueingConcurrentUpdates, +} from './ReactFiberConcurrentUpdates.new'; import { markNestedUpdateScheduled, @@ -521,10 +519,11 @@ function requestRetryLane(fiber: Fiber) { } export function scheduleUpdateOnFiber( + root: FiberRoot, fiber: Fiber, lane: Lane, eventTime: number, -): FiberRoot | null { +) { checkForNestedUpdates(); if (__DEV__) { @@ -533,11 +532,6 @@ export function scheduleUpdateOnFiber( } } - const root = markUpdateLaneFromFiberToRoot(fiber, lane); - if (root === null) { - return null; - } - if (__DEV__) { if (isFlushingPassiveEffects) { didScheduleUpdateDuringPassiveEffects = true; @@ -606,8 +600,6 @@ export function scheduleUpdateOnFiber( } if (root === workInProgressRoot) { - // TODO: Consolidate with `isInterleavedUpdate` check - // Received an update to a tree that's in the middle of rendering. Mark // that there was an interleaved update work on this root. Unless the // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render @@ -650,7 +642,6 @@ export function scheduleUpdateOnFiber( flushSyncCallbacksOnlyInLegacyMode(); } } - return root; } export function scheduleInitialHydrationOnRoot( @@ -673,73 +664,15 @@ export function scheduleInitialHydrationOnRoot( ensureRootIsScheduled(root, eventTime); } -// 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 markUpdateLaneFromFiberToRoot( - sourceFiber: Fiber, - lane: Lane, -): FiberRoot | null { - // Update the source fiber's lanes - sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); - let alternate = sourceFiber.alternate; - if (alternate !== null) { - alternate.lanes = mergeLanes(alternate.lanes, lane); - } - if (__DEV__) { - if ( - alternate === null && - (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } - } - // Walk the parent path to the root and update the child lanes. - let node = sourceFiber; - let parent = sourceFiber.return; - while (parent !== null) { - parent.childLanes = mergeLanes(parent.childLanes, lane); - alternate = parent.alternate; - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } else { - if (__DEV__) { - if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } - } - } - node = parent; - parent = parent.return; - } - if (node.tag === HostRoot) { - const root: FiberRoot = node.stateNode; - return root; - } else { - return null; - } -} - -export function isInterleavedUpdate(fiber: Fiber, lane: Lane) { +export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber) { + // Check if this is a render phase update. Only called by class components, + // which special (deprecated) behavior for UNSAFE_componentWillReceive props. return ( - // TODO: Optimize slightly by comparing to root that fiber belongs to. - // Requires some refactoring. Not a big deal though since it's rare for - // concurrent apps to have more than a single root. - (workInProgressRoot !== null || - // If the interleaved updates queue hasn't been cleared yet, then - // we should treat this as an interleaved update, too. This is also a - // defensive coding measure in case a new update comes in between when - // rendering has finished and when the interleaved updates are transferred - // to the main queue. - hasInterleavedUpdates()) && - (fiber.mode & ConcurrentMode) !== NoMode && - // If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps), - // then don't treat this as an interleaved update. This pattern is - // accompanied by a warning but we haven't fully deprecated it yet. We can - // remove once the deferRenderPhaseUpdateToNextBatch flag is enabled. - (deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext) + // TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We + // decided not to enable it. + (!deferRenderPhaseUpdateToNextBatch || + (fiber.mode & ConcurrentMode) === NoMode) && + (executionContext & RenderContext) !== NoContext ); } @@ -1541,7 +1474,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { workInProgressRootConcurrentErrors = null; workInProgressRootRecoverableErrors = null; - enqueueInterleavedUpdates(); + finishQueueingConcurrentUpdates(); if (__DEV__) { ReactStrictModeWarnings.discardPendingWarnings(); @@ -2113,9 +2046,15 @@ function commitRootImpl( root.callbackNode = null; root.callbackPriority = NoLane; - // Update the first and last pending times on this root. The new first - // pending time is whatever is left on the root fiber. + // Check which lanes no longer have any work scheduled on them, and mark + // those as finished. let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); + + // Make sure to account for lanes that were updated by a concurrent event + // during the render phase; don't mark them as finished. + const concurrentlyUpdatedLanes = finishQueueingConcurrentUpdates(); + remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); + markRootFinished(root, remainingLanes); if (root === workInProgressRoot) { @@ -2617,9 +2556,8 @@ function captureCommitPhaseErrorOnRoot( ) { const errorInfo = createCapturedValue(error, sourceFiber); const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane)); - enqueueUpdate(rootFiber, update, (SyncLane: Lane)); + const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2667,9 +2605,8 @@ export function captureCommitPhaseError( errorInfo, (SyncLane: Lane), ); - enqueueUpdate(fiber, update, (SyncLane: Lane)); + const root = enqueueUpdate(fiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2760,7 +2697,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { } // TODO: Special case idle priority? const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); + const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane); if (root !== null) { markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2931,7 +2868,7 @@ function invokeEffectsInDev( } let didWarnStateUpdateForNotYetMountedComponent: Set | null = null; -function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) { +export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) { if (__DEV__) { if ((executionContext & RenderContext) !== NoContext) { // We let the other warning about render phase updates deal with this one. diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 3e02a4fe37..5a9f93bbad 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -105,11 +105,9 @@ import { import {LegacyRoot} from './ReactRootTags'; import { NoFlags, - Placement, Incomplete, StoreConsistency, HostEffectMask, - Hydrating, ForceClientRender, BeforeMutationMask, MutationMask, @@ -182,7 +180,7 @@ import { invokePassiveEffectUnmountInDEV, reportUncaughtErrorInDEV, } from './ReactFiberCommitWork.old'; -import {enqueueUpdate} from './ReactUpdateQueue.old'; +import {enqueueUpdate} from './ReactFiberClassUpdateQueue.old'; import {resetContextDependencies} from './ReactFiberNewContext.old'; import { resetHooksAfterThrow, @@ -196,9 +194,9 @@ import { createCursor, } from './ReactFiberStack.old'; import { - enqueueInterleavedUpdates, - hasInterleavedUpdates, -} from './ReactFiberInterleavedUpdates.old'; + enqueueConcurrentRenderForLane, + finishQueueingConcurrentUpdates, +} from './ReactFiberConcurrentUpdates.old'; import { markNestedUpdateScheduled, @@ -521,10 +519,11 @@ function requestRetryLane(fiber: Fiber) { } export function scheduleUpdateOnFiber( + root: FiberRoot, fiber: Fiber, lane: Lane, eventTime: number, -): FiberRoot | null { +) { checkForNestedUpdates(); if (__DEV__) { @@ -533,11 +532,6 @@ export function scheduleUpdateOnFiber( } } - const root = markUpdateLaneFromFiberToRoot(fiber, lane); - if (root === null) { - return null; - } - if (__DEV__) { if (isFlushingPassiveEffects) { didScheduleUpdateDuringPassiveEffects = true; @@ -606,8 +600,6 @@ export function scheduleUpdateOnFiber( } if (root === workInProgressRoot) { - // TODO: Consolidate with `isInterleavedUpdate` check - // Received an update to a tree that's in the middle of rendering. Mark // that there was an interleaved update work on this root. Unless the // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render @@ -650,7 +642,6 @@ export function scheduleUpdateOnFiber( flushSyncCallbacksOnlyInLegacyMode(); } } - return root; } export function scheduleInitialHydrationOnRoot( @@ -673,73 +664,15 @@ export function scheduleInitialHydrationOnRoot( ensureRootIsScheduled(root, eventTime); } -// 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 markUpdateLaneFromFiberToRoot( - sourceFiber: Fiber, - lane: Lane, -): FiberRoot | null { - // Update the source fiber's lanes - sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); - let alternate = sourceFiber.alternate; - if (alternate !== null) { - alternate.lanes = mergeLanes(alternate.lanes, lane); - } - if (__DEV__) { - if ( - alternate === null && - (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags - ) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } - } - // Walk the parent path to the root and update the child lanes. - let node = sourceFiber; - let parent = sourceFiber.return; - while (parent !== null) { - parent.childLanes = mergeLanes(parent.childLanes, lane); - alternate = parent.alternate; - if (alternate !== null) { - alternate.childLanes = mergeLanes(alternate.childLanes, lane); - } else { - if (__DEV__) { - if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { - warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); - } - } - } - node = parent; - parent = parent.return; - } - if (node.tag === HostRoot) { - const root: FiberRoot = node.stateNode; - return root; - } else { - return null; - } -} - -export function isInterleavedUpdate(fiber: Fiber, lane: Lane) { +export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber) { + // Check if this is a render phase update. Only called by class components, + // which special (deprecated) behavior for UNSAFE_componentWillReceive props. return ( - // TODO: Optimize slightly by comparing to root that fiber belongs to. - // Requires some refactoring. Not a big deal though since it's rare for - // concurrent apps to have more than a single root. - (workInProgressRoot !== null || - // If the interleaved updates queue hasn't been cleared yet, then - // we should treat this as an interleaved update, too. This is also a - // defensive coding measure in case a new update comes in between when - // rendering has finished and when the interleaved updates are transferred - // to the main queue. - hasInterleavedUpdates()) && - (fiber.mode & ConcurrentMode) !== NoMode && - // If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps), - // then don't treat this as an interleaved update. This pattern is - // accompanied by a warning but we haven't fully deprecated it yet. We can - // remove once the deferRenderPhaseUpdateToNextBatch flag is enabled. - (deferRenderPhaseUpdateToNextBatch || - (executionContext & RenderContext) === NoContext) + // TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We + // decided not to enable it. + (!deferRenderPhaseUpdateToNextBatch || + (fiber.mode & ConcurrentMode) === NoMode) && + (executionContext & RenderContext) !== NoContext ); } @@ -1541,7 +1474,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { workInProgressRootConcurrentErrors = null; workInProgressRootRecoverableErrors = null; - enqueueInterleavedUpdates(); + finishQueueingConcurrentUpdates(); if (__DEV__) { ReactStrictModeWarnings.discardPendingWarnings(); @@ -2617,9 +2550,8 @@ function captureCommitPhaseErrorOnRoot( ) { const errorInfo = createCapturedValue(error, sourceFiber); const update = createRootErrorUpdate(rootFiber, errorInfo, (SyncLane: Lane)); - enqueueUpdate(rootFiber, update, (SyncLane: Lane)); + const root = enqueueUpdate(rootFiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(rootFiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2667,9 +2599,8 @@ export function captureCommitPhaseError( errorInfo, (SyncLane: Lane), ); - enqueueUpdate(fiber, update, (SyncLane: Lane)); + const root = enqueueUpdate(fiber, update, (SyncLane: Lane)); const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(fiber, (SyncLane: Lane)); if (root !== null) { markRootUpdated(root, SyncLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2760,7 +2691,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { } // TODO: Special case idle priority? const eventTime = requestEventTime(); - const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane); + const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane); if (root !== null) { markRootUpdated(root, retryLane, eventTime); ensureRootIsScheduled(root, eventTime); @@ -2931,7 +2862,7 @@ function invokeEffectsInDev( } let didWarnStateUpdateForNotYetMountedComponent: Set | null = null; -function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) { +export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) { if (__DEV__) { if ((executionContext & RenderContext) !== NoContext) { // We let the other warning about render phase updates deal with this one. diff --git a/scripts/merge-fork/forked-revisions b/scripts/merge-fork/forked-revisions index e69de29bb2..76d977e1f1 100644 --- a/scripts/merge-fork/forked-revisions +++ b/scripts/merge-fork/forked-revisions @@ -0,0 +1 @@ +17691acc071d56261d43c3cf183f287d983baa9b [FORKED] Don't update childLanes until after current render