Skip to content

Commit

Permalink
Initial Lanes implementation (#18796)
Browse files Browse the repository at this point in the history
See PR #18796 for more information.

All of the changes I've made in this commit are behind the
`enableNewReconciler` flag. Merging this to master will not affect the
open source builds or the build that we ship to Facebook.

The only build that is affected is the `ReactDOMForked` build, which is
deployed to Facebook **behind an experimental flag (currently disabled
for all users)**. We will use this flag to gradually roll out the new
reconciler, and quickly roll it back if we find any problems.

Because we have those protections in place, what I'm aiming for with
this initial PR is the **smallest possible atomic change that lands
cleanly and doesn't rely on too many hacks**. The goal has not been to
get every single test or feature passing, and it definitely is not to
implement all the features that we intend to build on top of the new
model. When possible, I have chosen to preserve existing semantics and
defer changes to follow-up steps. (Listed in the section below.)

(I did not end up having to disable any tests, although if I had, that
should not have necessarily been a merge blocker.)

For example, even though one of the primary goals of this project is to
improve our model for parallel Suspense transitions, in this initial
implementation, I have chosen to keep the same core heuristics for
sequencing and flushing that existed in the ExpirationTimes model: low
priority updates cannot finish without also finishing high priority
ones.

Despite all these precautions, **because the scope of this refactor is
inherently large, I do expect we will find regressions.** The flip side
is that I also expect the new model to improve the stability of the
codebase and make it easier to fix bugs when they arise.
  • Loading branch information
acdlite committed May 3, 2020
1 parent 3c7d52c commit 93e078d
Show file tree
Hide file tree
Showing 28 changed files with 1,956 additions and 2,271 deletions.
Expand Up @@ -1306,44 +1306,25 @@ describe('ReactDOMServerHooks', () => {
// State update should trigger the ID to update, which changes the props
// of ChildWithID. This should cause ChildWithID to hydrate before Children

gate(flags => {
if (__DEV__) {
expect(Scheduler).toFlushAndYieldThrough([
'Child with ID',
// Fallbacks are immdiately committed in TestUtils version
// of act
// 'Child with ID',
// 'Child with ID',
'Child One',
'Child Two',
]);
} else if (flags.new) {
// Upgrading a dehyrdating boundary works a little differently in
// the new reconciler. After the update on the boundary is
// scheduled, it waits until the end of the current time slice
// before restarting at the higher priority.
expect(Scheduler).toFlushAndYieldThrough([
'Child with ID',
'Child with ID',
'Child with ID',
'Child with ID',
'Child One',
'Child Two',
]);
} else {
// Whereas the old reconciler relies on a Scheduler hack to
// interrupt the current task. It's not clear if this is any
// better or worse, though. Regardless it's not a big deal since
// the time slices aren't that big.
expect(Scheduler).toFlushAndYieldThrough([
'Child with ID',
'Child with ID',
'Child with ID',
'Child One',
'Child Two',
]);
}
});
expect(Scheduler).toFlushAndYieldThrough(
__DEV__
? [
'Child with ID',
// Fallbacks are immediately committed in TestUtils version
// of act
// 'Child with ID',
// 'Child with ID',
'Child One',
'Child Two',
]
: [
'Child with ID',
'Child with ID',
'Child with ID',
'Child One',
'Child Two',
],
);

expect(child1Ref.current).toBe(null);
expect(childWithIDRef.current).toEqual(
Expand Down Expand Up @@ -1691,15 +1672,24 @@ describe('ReactDOMServerHooks', () => {

ReactDOM.createRoot(container, {hydrate: true}).render(<App />);

expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
if (gate(flags => flags.new)) {
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
),
).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
]);
]);
} else {
// In the old reconciler, the error isn't surfaced to the user. That
// part isn't important, as long as It warns.
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
),
).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
]);
}
});

it('useOpaqueIdentifier throws if you try to add the result as a number in a child component wrapped in a Suspense', async () => {
Expand All @@ -1724,15 +1714,24 @@ describe('ReactDOMServerHooks', () => {

ReactDOM.createRoot(container, {hydrate: true}).render(<App />);

expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
if (gate(flags => flags.new)) {
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
),
).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
]);
]);
} else {
// In the old reconciler, the error isn't surfaced to the user. That
// part isn't important, as long as It warns.
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
),
).toErrorDev([
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
'Do not read the value directly.',
]);
}
});

it('useOpaqueIdentifier with two opaque identifiers on the same page', () => {
Expand Down

0 comments on commit 93e078d

Please sign in to comment.