Skip to content

Commit

Permalink
Basic version of resuming for top-level prerender
Browse files Browse the repository at this point in the history
Implements a limited version of resuming that only applies to the root,
to account for the pathological case where a prerendered root must be
completely restarted before it can commit. This won't be necessary
once we implement resuming for real.
  • Loading branch information
acdlite committed Oct 16, 2017
1 parent 8c452d8 commit decebe3
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 6 deletions.
20 changes: 20 additions & 0 deletions src/renderers/dom/shared/__tests__/ReactDOMRoot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,24 @@ describe('ReactDOMRoot', () => {
work.commit();
expect(container.textContent).toEqual('Hi');
});

it("does not restart a blocked root that wasn't updated", () => {
let ops = [];
function Foo(props) {
ops.push('Foo');
return props.children;
}
const root = ReactDOM.createRoot(container);
const work = root.prerender(<Foo>Hi</Foo>);
expect(ops).toEqual(['Foo']);
// Hasn't updated yet
expect(container.textContent).toEqual('');

ops = [];

// Flush work. Shouldn't re-render Foo.
work.commit();
expect(ops).toEqual([]);
expect(container.textContent).toEqual('Hi');
});
});
13 changes: 11 additions & 2 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}

function updateHostRoot(current, workInProgress, renderExpirationTime) {
const root: FiberRoot = workInProgress.stateNode;
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
Expand All @@ -331,14 +332,23 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
null,
renderExpirationTime,
);
memoizeState(workInProgress, state);
if (root.completedAt === renderExpirationTime) {
// The root is already complete. Bail out and commit.
// TODO: This is a limited version of resuming that only applies to
// the root, to account for the pathological case where a completed
// root must be completely restarted before it can commit. Once we
// implement resuming for real, this special branch shouldn't
// be neccessary.
return null;
}
if (prevState === state) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const element = state.element;
const root: FiberRoot = workInProgress.stateNode;
if (
(current === null || current.child === null) &&
root.hydrate &&
Expand Down Expand Up @@ -370,7 +380,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
resetHydrationState();
reconcileChildren(current, workInProgress, element);
}
memoizeState(workInProgress, state);
return workInProgress.child;
}
resetHydrationState();
Expand Down
3 changes: 3 additions & 0 deletions src/renderers/shared/fiber/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type FiberRoot = {
// Determines if this root was blocked from committing.
isBlocked: boolean,
// The time at which this root completed.
// TODO: Remove once we add back resuming.
completedAt: ExpirationTime,
forceExpire: ExpirationTime,
// The work schedule is a linked list.
nextScheduledRoot: FiberRoot | null,
Expand All @@ -48,6 +50,7 @@ exports.createFiberRoot = function(
containerInfo: containerInfo,
isScheduled: false,
isBlocked: false,
completedAt: NoWork,
forceExpire: NoWork,
nextScheduledRoot: null,
context: null,
Expand Down
26 changes: 22 additions & 4 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,26 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// unfortunately this is it.
resetContextStack();

nextUnitOfWork = createWorkInProgress(
earliestExpirationRoot.current,
earliestExpirationTime,
);
if (earliestExpirationRoot.completedAt === nextRenderExpirationTime) {
// If the root is already complete, reuse the existing work-in-progress.
// TODO: This is a limited version of resuming that only applies to
// the root, to account for the pathological case where a completed
// root must be completely restarted before it can commit. Once we
// implement resuming for real, this special branch shouldn't
// be neccessary.
nextUnitOfWork = earliestExpirationRoot.current.alternate;
invariant(
nextUnitOfWork !== null,
'Expected a completed root to have a work-in-progress. This error ' +
'is likely caused by a bug in React. Please file an issue.',
);
} else {
nextUnitOfWork = createWorkInProgress(
earliestExpirationRoot.current,
earliestExpirationTime,
);
}

if (earliestExpirationRoot !== nextRenderedTree) {
// We've switched trees. Reset the nested update counter.
nestedUpdateCount = 0;
Expand Down Expand Up @@ -462,6 +478,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
'related to the return field. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
root.completedAt = NoWork;

if (nextRenderExpirationTime <= mostRecentCurrentTime) {
// Keep track of the number of iterations to prevent an infinite
Expand Down Expand Up @@ -718,6 +735,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
} else {
// We've reached the root.
const root: FiberRoot = workInProgress.stateNode;
root.completedAt = nextRenderExpirationTime;
if (nextCommitIsBlocked) {
root.isBlocked = true;
} else {
Expand Down

0 comments on commit decebe3

Please sign in to comment.