Skip to content

Commit

Permalink
SuspenseList Optimizations (#16005)
Browse files Browse the repository at this point in the history
* Add a bunch of optimizations to SuspenseList

We now are able to bail out of reconciliation and splitting out the tail
during deep updates that hasn't changed the child props. This only
works while the list wasn't suspended before.

I also moved the second render of the "head" to the complete phase. This
cleans it up a bit for the tail collapsing PR.

For this second pass I also use a new technique of resetting the child
Fibers for the second pass. This is effectively a fast path to avoid
reconciling the children against props again.

* Move to didSuspend from SuspenseListState to the effectTag

The effectTag now tracks whether the previous commit was suspended.

This frees up SuspenseListState to be render-phase only state.

We use null to mean the default "independent" mode.

* Rename to SuspenseListState to SuspenseListRenderState

* Reuse SuspenseListRenderState across render passes

* Add optimization to bail out of scanning children if they can't be suspended

This optimized the deep update case or initial render without anything
suspending.

We have some information available to us that tell us if nothing has
suspended in the past and nothing has suspended this render pass.

This also fixes a bug where we didn't tag the previous render as having
suspended boundaries if we didn't need to force a rerender.

* rm printChildren

oops
  • Loading branch information
sebmarkbage committed Jul 1, 2019
1 parent fbbbea1 commit 933c664
Show file tree
Hide file tree
Showing 7 changed files with 386 additions and 256 deletions.
13 changes: 13 additions & 0 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import warningWithoutStack from 'shared/warningWithoutStack';

import {
createWorkInProgress,
resetWorkInProgress,
createFiberFromElement,
createFiberFromFragment,
createFiberFromText,
Expand Down Expand Up @@ -1386,3 +1387,15 @@ export function cloneChildFibers(
}
newChild.sibling = null;
}

// Reset a workInProgress child set to prepare it for a second pass.
export function resetChildFibers(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): void {
let child = workInProgress.child;
while (child !== null) {
resetWorkInProgress(child, renderExpirationTime);
child = child.sibling;
}
}
73 changes: 73 additions & 0 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,79 @@ export function createWorkInProgress(
return workInProgress;
}

// Used to reuse a Fiber for a second pass.
export function resetWorkInProgress(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// This resets the Fiber to what createFiber or createWorkInProgress would
// have set the values to before during the first pass. Ideally this wouldn't
// be necessary but unfortunately many code paths reads from the workInProgress
// when they should be reading from current and writing to workInProgress.

// We assume pendingProps, index, key, ref, return are still untouched to
// avoid doing another reconciliation.

// Reset the effect tag.
workInProgress.effectTag = NoEffect;

// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;

let current = workInProgress.alternate;
if (current === null) {
// Reset to createFiber's initial values.
workInProgress.childExpirationTime = NoWork;
workInProgress.expirationTime = renderExpirationTime;

workInProgress.child = null;
workInProgress.memoizedProps = null;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;

workInProgress.dependencies = null;

if (enableProfilerTimer) {
// Note: We don't reset the actualTime counts. It's useful to accumulate
// actual time across multiple render passes.
workInProgress.selfBaseDuration = 0;
workInProgress.treeBaseDuration = 0;
}
} else {
// Reset to the cloned values that createWorkInProgress would've.
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;

workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;

// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
events: currentDependencies.events,
};

if (enableProfilerTimer) {
// Note: We don't reset the actualTime counts. It's useful to accumulate
// actual time across multiple render passes.
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
}

return workInProgress;
}

export function createHostRootFiber(tag: RootTag): Fiber {
let mode;
if (tag === ConcurrentRoot) {
Expand Down

0 comments on commit 933c664

Please sign in to comment.