Skip to content

Commit 1114b6e

Browse files
committed
Initial Lanes implementation
A refactor of our concurrency and scheduling architecture. Replaces the ExpirationTime type with a new concept, called Lanes. 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.
1 parent e52b376 commit 1114b6e

27 files changed

+1935
-2247
lines changed

packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,44 +1306,25 @@ describe('ReactDOMServerHooks', () => {
13061306
// State update should trigger the ID to update, which changes the props
13071307
// of ChildWithID. This should cause ChildWithID to hydrate before Children
13081308

1309-
gate(flags => {
1310-
if (__DEV__) {
1311-
expect(Scheduler).toFlushAndYieldThrough([
1312-
'Child with ID',
1313-
// Fallbacks are immdiately committed in TestUtils version
1314-
// of act
1315-
// 'Child with ID',
1316-
// 'Child with ID',
1317-
'Child One',
1318-
'Child Two',
1319-
]);
1320-
} else if (flags.new) {
1321-
// Upgrading a dehyrdating boundary works a little differently in
1322-
// the new reconciler. After the update on the boundary is
1323-
// scheduled, it waits until the end of the current time slice
1324-
// before restarting at the higher priority.
1325-
expect(Scheduler).toFlushAndYieldThrough([
1326-
'Child with ID',
1327-
'Child with ID',
1328-
'Child with ID',
1329-
'Child with ID',
1330-
'Child One',
1331-
'Child Two',
1332-
]);
1333-
} else {
1334-
// Whereas the old reconciler relies on a Scheduler hack to
1335-
// interrupt the current task. It's not clear if this is any
1336-
// better or worse, though. Regardless it's not a big deal since
1337-
// the time slices aren't that big.
1338-
expect(Scheduler).toFlushAndYieldThrough([
1339-
'Child with ID',
1340-
'Child with ID',
1341-
'Child with ID',
1342-
'Child One',
1343-
'Child Two',
1344-
]);
1345-
}
1346-
});
1309+
expect(Scheduler).toFlushAndYieldThrough(
1310+
__DEV__
1311+
? [
1312+
'Child with ID',
1313+
// Fallbacks are immediately committed in TestUtils version
1314+
// of act
1315+
// 'Child with ID',
1316+
// 'Child with ID',
1317+
'Child One',
1318+
'Child Two',
1319+
]
1320+
: [
1321+
'Child with ID',
1322+
'Child with ID',
1323+
'Child with ID',
1324+
'Child One',
1325+
'Child Two',
1326+
],
1327+
);
13471328

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

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

1694-
expect(() =>
1695-
expect(() => Scheduler.unstable_flushAll()).toThrow(
1675+
if (gate(flags => flags.new)) {
1676+
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
16961677
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
16971678
'Do not read the value directly.',
1698-
),
1699-
).toErrorDev([
1700-
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1701-
'Do not read the value directly.',
1702-
]);
1679+
]);
1680+
} else {
1681+
// In the old reconciler, the error isn't surfaced to the user. That
1682+
// part isn't important, as long as It warns.
1683+
expect(() =>
1684+
expect(() => Scheduler.unstable_flushAll()).toThrow(
1685+
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1686+
'Do not read the value directly.',
1687+
),
1688+
).toErrorDev([
1689+
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1690+
'Do not read the value directly.',
1691+
]);
1692+
}
17031693
});
17041694

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

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

1727-
expect(() =>
1728-
expect(() => Scheduler.unstable_flushAll()).toThrow(
1717+
if (gate(flags => flags.new)) {
1718+
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
17291719
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
17301720
'Do not read the value directly.',
1731-
),
1732-
).toErrorDev([
1733-
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1734-
'Do not read the value directly.',
1735-
]);
1721+
]);
1722+
} else {
1723+
// In the old reconciler, the error isn't surfaced to the user. That
1724+
// part isn't important, as long as It warns.
1725+
expect(() =>
1726+
expect(() => Scheduler.unstable_flushAll()).toThrow(
1727+
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1728+
'Do not read the value directly.',
1729+
),
1730+
).toErrorDev([
1731+
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
1732+
'Do not read the value directly.',
1733+
]);
1734+
}
17361735
});
17371736

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

0 commit comments

Comments
 (0)