Skip to content

Commit

Permalink
Convert mutation phase to depth-first traversal (#20596)
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite authored Jan 15, 2021
1 parent 6132919 commit 95feb0e
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 170 deletions.
13 changes: 2 additions & 11 deletions packages/react-reconciler/src/ReactChildFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,22 +282,13 @@ function ChildReconciler(shouldTrackSideEffects) {
childToDelete.nextEffect = null;
childToDelete.flags = (childToDelete.flags & StaticMask) | Deletion;

let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [childToDelete];
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
// Stash a reference to the return fiber's deletion array on each of the
// deleted children. This is really weird, but it's a temporary workaround
// while we're still using the effect list to traverse effect fibers. A
// better workaround would be to follow the `.return` pointer in the commit
// phase, but unfortunately we can't assume that `.return` points to the
// correct fiber, even in the commit phase, because `findDOMNode` might
// mutate it.
// TODO: Remove this line.
childToDelete.deletions = deletions;
}

function deleteRemainingChildren(
Expand Down
10 changes: 4 additions & 6 deletions packages/react-reconciler/src/ReactFiberBeginWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -2203,14 +2203,13 @@ function updateSuspensePrimaryChildren(
currentFallbackChildFragment.flags =
(currentFallbackChildFragment.flags & StaticMask) | Deletion;
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChildFragment;
let deletions = workInProgress.deletions;
const deletions = workInProgress.deletions;
if (deletions === null) {
deletions = workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.flags |= ChildDeletion;
} else {
deletions.push(currentFallbackChildFragment);
}
currentFallbackChildFragment.deletions = deletions;
}

workInProgress.child = primaryChildFragment;
Expand Down Expand Up @@ -3194,14 +3193,13 @@ function remountFiber(
current.nextEffect = null;
current.flags = (current.flags & StaticMask) | Deletion;

let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [current];
returnFiber.deletions = [current];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(current);
}
current.deletions = deletions;

newWorkInProgress.flags |= Placement;

Expand Down
170 changes: 170 additions & 0 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,16 @@ import {
NoFlags,
ContentReset,
Placement,
PlacementAndUpdate,
ChildDeletion,
Snapshot,
Update,
Callback,
Ref,
Hydrating,
HydratingAndUpdate,
Passive,
MutationMask,
PassiveMask,
LayoutMask,
PassiveUnmountPendingDev,
Expand Down Expand Up @@ -1841,6 +1845,172 @@ function commitResetTextContent(current: Fiber) {
resetTextContent(current.stateNode);
}

export function commitMutationEffects(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
firstChild: Fiber,
) {
nextEffect = firstChild;
commitMutationEffects_begin(root, renderPriorityLevel);
}

function commitMutationEffects_begin(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
while (nextEffect !== null) {
const fiber = nextEffect;

// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
if (__DEV__) {
invokeGuardedCallback(
null,
commitDeletion,
null,
root,
childToDelete,
renderPriorityLevel,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(childToDelete, error);
}
} else {
try {
commitDeletion(root, childToDelete, renderPriorityLevel);
} catch (error) {
captureCommitPhaseError(childToDelete, error);
}
}
}
}

const child = fiber.child;
if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else {
commitMutationEffects_complete(root, renderPriorityLevel);
}
}
}

function commitMutationEffects_complete(
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
if (__DEV__) {
setCurrentDebugFiberInDEV(fiber);
invokeGuardedCallback(
null,
commitMutationEffectsOnFiber,
null,
fiber,
root,
renderPriorityLevel,
);
if (hasCaughtError()) {
const error = clearCaughtError();
captureCommitPhaseError(fiber, error);
}
resetCurrentDebugFiberInDEV();
} else {
try {
commitMutationEffectsOnFiber(fiber, root, renderPriorityLevel);
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}

const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}

nextEffect = fiber.return;
}
}

function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
renderPriorityLevel: ReactPriorityLevel,
) {
const flags = finishedWork.flags;

if (flags & ContentReset) {
commitResetTextContent(finishedWork);
}

if (flags & Ref) {
const current = finishedWork.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
if (finishedWork.tag === ScopeComponent) {
commitAttachRef(finishedWork);
}
}
}

// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryFlags = flags & (Placement | Update | Hydrating);
outer: switch (primaryFlags) {
case Placement: {
commitPlacement(finishedWork);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
finishedWork.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
finishedWork.flags &= ~Placement;

// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Hydrating: {
finishedWork.flags &= ~Hydrating;
break;
}
case HydratingAndUpdate: {
finishedWork.flags &= ~Hydrating;

// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
}
}

export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,13 @@ function deleteHydratableInstance(
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}

let deletions = returnFiber.deletions;
const deletions = returnFiber.deletions;
if (deletions === null) {
deletions = returnFiber.deletions = [childToDelete];
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
childToDelete.deletions = deletions;
}

function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
Expand Down
Loading

0 comments on commit 95feb0e

Please sign in to comment.