diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 84a20638ef299..fb4c942ab6eea 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -67,9 +67,15 @@ import { Placement, Snapshot, Update, + Passive, + PassiveMask, } from './ReactFiberFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; +import { + resetCurrentFiber as resetCurrentDebugFiberInDEV, + setCurrentFiber as setCurrentDebugFiberInDEV, +} from './ReactCurrentFiber'; import {onCommitUnmount} from './ReactFiberDevToolsHook.new'; import {resolveDefaultProps} from './ReactFiberLazyComponent.new'; @@ -78,6 +84,8 @@ import { getCommitTime, recordLayoutEffectDuration, startLayoutEffectTimer, + recordPassiveEffectDuration, + startPassiveEffectTimer, } from './ReactProfilerTimer.new'; import {ProfileMode} from './ReactTypeOfMode'; import {commitUpdateQueue} from './ReactUpdateQueue.new'; @@ -134,6 +142,8 @@ if (__DEV__) { const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; +let nextEffect: Fiber | null = null; + const callComponentWillUnmountWithTimer = function(current, instance) { instance.props = current.memoizedProps; instance.state = current.memoizedState; @@ -1798,6 +1808,117 @@ function commitResetTextContent(current: Fiber) { resetTextContent(current.stateNode); } +export function commitPassiveMountEffects( + root: FiberRoot, + firstChild: Fiber, +): void { + nextEffect = firstChild; + commitPassiveMountEffects_begin(firstChild, root); +} + +function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { + while (nextEffect !== null) { + const fiber = nextEffect; + const firstChild = fiber.child; + if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) { + ensureCorrectReturnPointer(firstChild, fiber); + nextEffect = firstChild; + } else { + commitPassiveMountEffects_complete(subtreeRoot, root); + } + } +} + +function commitPassiveMountEffects_complete( + subtreeRoot: Fiber, + root: FiberRoot, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + if ((fiber.flags & Passive) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitPassiveMountOnFiber, + null, + root, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitPassiveMountOnFiber(root, fiber); + } catch (error) { + captureCommitPhaseError(fiber, error); + } + } + } + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + ensureCorrectReturnPointer(sibling, fiber.return); + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function commitPassiveMountOnFiber( + finishedRoot: FiberRoot, + finishedWork: Fiber, +): void { + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + startPassiveEffectTimer(); + try { + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork); + } finally { + recordPassiveEffectDuration(finishedWork); + } + } else { + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork); + } + break; + } + } +} + +let didWarnWrongReturnPointer = false; +function ensureCorrectReturnPointer(fiber, expectedReturnFiber) { + if (__DEV__) { + if (!didWarnWrongReturnPointer && fiber.return !== expectedReturnFiber) { + didWarnWrongReturnPointer = true; + console.error( + 'Internal React error: Return pointer is inconsistent ' + + 'with parent.', + ); + } + } + + // TODO: Remove this assignment once we're confident that it won't break + // anything, by checking the warning logs for the above invariant + fiber.return = expectedReturnFiber; +} + export { commitBeforeMutationLifeCycles, commitResetTextContent, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 94d137c99bdc2..7f98172a30edb 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -67,9 +67,15 @@ import { Placement, Snapshot, Update, + Passive, + PassiveMask, } from './ReactFiberFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; +import { + resetCurrentFiber as resetCurrentDebugFiberInDEV, + setCurrentFiber as setCurrentDebugFiberInDEV, +} from './ReactCurrentFiber'; import {onCommitUnmount} from './ReactFiberDevToolsHook.old'; import {resolveDefaultProps} from './ReactFiberLazyComponent.old'; @@ -78,6 +84,8 @@ import { getCommitTime, recordLayoutEffectDuration, startLayoutEffectTimer, + recordPassiveEffectDuration, + startPassiveEffectTimer, } from './ReactProfilerTimer.old'; import {ProfileMode} from './ReactTypeOfMode'; import {commitUpdateQueue} from './ReactUpdateQueue.old'; @@ -134,6 +142,8 @@ if (__DEV__) { const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; +let nextEffect: Fiber | null = null; + const callComponentWillUnmountWithTimer = function(current, instance) { instance.props = current.memoizedProps; instance.state = current.memoizedState; @@ -1798,6 +1808,117 @@ function commitResetTextContent(current: Fiber) { resetTextContent(current.stateNode); } +export function commitPassiveMountEffects( + root: FiberRoot, + firstChild: Fiber, +): void { + nextEffect = firstChild; + commitPassiveMountEffects_begin(firstChild, root); +} + +function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) { + while (nextEffect !== null) { + const fiber = nextEffect; + const firstChild = fiber.child; + if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) { + ensureCorrectReturnPointer(firstChild, fiber); + nextEffect = firstChild; + } else { + commitPassiveMountEffects_complete(subtreeRoot, root); + } + } +} + +function commitPassiveMountEffects_complete( + subtreeRoot: Fiber, + root: FiberRoot, +) { + while (nextEffect !== null) { + const fiber = nextEffect; + if ((fiber.flags & Passive) !== NoFlags) { + if (__DEV__) { + setCurrentDebugFiberInDEV(fiber); + invokeGuardedCallback( + null, + commitPassiveMountOnFiber, + null, + root, + fiber, + ); + if (hasCaughtError()) { + const error = clearCaughtError(); + captureCommitPhaseError(fiber, error); + } + resetCurrentDebugFiberInDEV(); + } else { + try { + commitPassiveMountOnFiber(root, fiber); + } catch (error) { + captureCommitPhaseError(fiber, error); + } + } + } + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + ensureCorrectReturnPointer(sibling, fiber.return); + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + +function commitPassiveMountOnFiber( + finishedRoot: FiberRoot, + finishedWork: Fiber, +): void { + switch (finishedWork.tag) { + case FunctionComponent: + case ForwardRef: + case SimpleMemoComponent: { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + finishedWork.mode & ProfileMode + ) { + startPassiveEffectTimer(); + try { + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork); + } finally { + recordPassiveEffectDuration(finishedWork); + } + } else { + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork); + } + break; + } + } +} + +let didWarnWrongReturnPointer = false; +function ensureCorrectReturnPointer(fiber, expectedReturnFiber) { + if (__DEV__) { + if (!didWarnWrongReturnPointer && fiber.return !== expectedReturnFiber) { + didWarnWrongReturnPointer = true; + console.error( + 'Internal React error: Return pointer is inconsistent ' + + 'with parent.', + ); + } + } + + // TODO: Remove this assignment once we're confident that it won't break + // anything, by checking the warning logs for the above invariant + fiber.return = expectedReturnFiber; +} + export { commitBeforeMutationLifeCycles, commitResetTextContent, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 042361ebdb1b3..3177abb63c511 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -193,6 +193,7 @@ import { commitPassiveEffectDurations, commitResetTextContent, isSuspenseBoundaryBeingHidden, + commitPassiveMountEffects, } from './ReactFiberCommitWork.new'; import {enqueueUpdate} from './ReactUpdateQueue.new'; import {resetContextDependencies} from './ReactFiberNewContext.new'; @@ -339,7 +340,6 @@ let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority; let pendingPassiveEffectsLanes: Lanes = NoLanes; -let pendingPassiveHookEffectsMount: Array = []; let pendingPassiveHookEffectsUnmount: Array = []; let pendingPassiveProfilerEffects: Array = []; @@ -2489,7 +2489,6 @@ export function enqueuePendingPassiveHookEffectMount( fiber: Fiber, effect: HookEffect, ): void { - pendingPassiveHookEffectsMount.push(effect, fiber); if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { @@ -2520,11 +2519,6 @@ export function enqueuePendingPassiveHookEffectUnmount( } } -function invokePassiveEffectCreate(effect: HookEffect): void { - const create = effect.create; - effect.destroy = create(); -} - function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; @@ -2626,68 +2620,9 @@ function flushPassiveEffectsImpl() { } } // Second pass: Create new passive effects. - const mountEffects = pendingPassiveHookEffectsMount; - pendingPassiveHookEffectsMount = []; - for (let i = 0; i < mountEffects.length; i += 2) { - const effect = ((mountEffects[i]: any): HookEffect); - const fiber = ((mountEffects[i + 1]: any): Fiber); - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - startPassiveEffectTimer(); - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - recordPassiveEffectDuration(fiber); - } else { - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - } - if (hasCaughtError()) { - invariant(fiber !== null, 'Should be working on an effect.'); - const error = clearCaughtError(); - captureCommitPhaseError(fiber, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - const create = effect.create; - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startPassiveEffectTimer(); - effect.destroy = create(); - } finally { - recordPassiveEffectDuration(fiber); - } - } else { - effect.destroy = create(); - } - } catch (error) { - invariant(fiber !== null, 'Should be working on an effect.'); - captureCommitPhaseError(fiber, error); - } - } - } - - // Note: This currently assumes there are no passive effects on the root fiber - // because the root is not part of its own effect list. - // This could change in the future. - let effect = root.current.firstEffect; - while (effect !== null) { - const nextNextEffect = effect.nextEffect; - // Remove nextEffect pointer to assist GC - effect.nextEffect = null; - if (effect.flags & Deletion) { - detachFiberAfterEffects(effect); - } - effect = nextNextEffect; - } + commitPassiveMountEffects(root, root.current); + // TODO: Move to commitPassiveMountEffects if (enableProfilerTimer && enableProfilerCommitHooks) { const profilerEffects = pendingPassiveProfilerEffects; pendingPassiveProfilerEffects = []; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 65157db61925d..dc21ff6b7f3ed 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -193,6 +193,7 @@ import { commitPassiveEffectDurations, commitResetTextContent, isSuspenseBoundaryBeingHidden, + commitPassiveMountEffects, } from './ReactFiberCommitWork.old'; import {enqueueUpdate} from './ReactUpdateQueue.old'; import {resetContextDependencies} from './ReactFiberNewContext.old'; @@ -339,7 +340,6 @@ let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority; let pendingPassiveEffectsLanes: Lanes = NoLanes; -let pendingPassiveHookEffectsMount: Array = []; let pendingPassiveHookEffectsUnmount: Array = []; let pendingPassiveProfilerEffects: Array = []; @@ -2489,7 +2489,6 @@ export function enqueuePendingPassiveHookEffectMount( fiber: Fiber, effect: HookEffect, ): void { - pendingPassiveHookEffectsMount.push(effect, fiber); if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { @@ -2520,11 +2519,6 @@ export function enqueuePendingPassiveHookEffectUnmount( } } -function invokePassiveEffectCreate(effect: HookEffect): void { - const create = effect.create; - effect.destroy = create(); -} - function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; @@ -2626,68 +2620,9 @@ function flushPassiveEffectsImpl() { } } // Second pass: Create new passive effects. - const mountEffects = pendingPassiveHookEffectsMount; - pendingPassiveHookEffectsMount = []; - for (let i = 0; i < mountEffects.length; i += 2) { - const effect = ((mountEffects[i]: any): HookEffect); - const fiber = ((mountEffects[i + 1]: any): Fiber); - if (__DEV__) { - setCurrentDebugFiberInDEV(fiber); - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - startPassiveEffectTimer(); - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - recordPassiveEffectDuration(fiber); - } else { - invokeGuardedCallback(null, invokePassiveEffectCreate, null, effect); - } - if (hasCaughtError()) { - invariant(fiber !== null, 'Should be working on an effect.'); - const error = clearCaughtError(); - captureCommitPhaseError(fiber, error); - } - resetCurrentDebugFiberInDEV(); - } else { - try { - const create = effect.create; - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - fiber.mode & ProfileMode - ) { - try { - startPassiveEffectTimer(); - effect.destroy = create(); - } finally { - recordPassiveEffectDuration(fiber); - } - } else { - effect.destroy = create(); - } - } catch (error) { - invariant(fiber !== null, 'Should be working on an effect.'); - captureCommitPhaseError(fiber, error); - } - } - } - - // Note: This currently assumes there are no passive effects on the root fiber - // because the root is not part of its own effect list. - // This could change in the future. - let effect = root.current.firstEffect; - while (effect !== null) { - const nextNextEffect = effect.nextEffect; - // Remove nextEffect pointer to assist GC - effect.nextEffect = null; - if (effect.flags & Deletion) { - detachFiberAfterEffects(effect); - } - effect = nextNextEffect; - } + commitPassiveMountEffects(root, root.current); + // TODO: Move to commitPassiveMountEffects if (enableProfilerTimer && enableProfilerCommitHooks) { const profilerEffects = pendingPassiveProfilerEffects; pendingPassiveProfilerEffects = [];