Skip to content

Commit f31005d

Browse files
authored
Add SyncHydrationLane (#25698)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> For more context: #25692 Based on #25695. This PR adds the `SyncHydrationLane` so we rewind on sync updates during selective hydration. Also added tests for ContinuouseHydration and DefaultHydration lanes. ## How did you test this change? <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> yarn test
1 parent f284d9f commit f31005d

File tree

8 files changed

+532
-406
lines changed

8 files changed

+532
-406
lines changed

packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js

+220-220
Large diffs are not rendered by default.

packages/react-devtools-shared/src/__tests__/preprocessData-test.js

+86-86
Large diffs are not rendered by default.

packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js

+115
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let Suspense;
2121
let act;
2222

2323
let IdleEventPriority;
24+
let ContinuousEventPriority;
2425

2526
function dispatchMouseHoverEvent(to, from) {
2627
if (!to) {
@@ -110,6 +111,18 @@ function TODO_scheduleIdleDOMSchedulerTask(fn) {
110111
});
111112
}
112113

114+
function TODO_scheduleContinuousSchedulerTask(fn) {
115+
ReactDOM.unstable_runWithPriority(ContinuousEventPriority, () => {
116+
const prevEvent = window.event;
117+
window.event = {type: 'message'};
118+
try {
119+
fn();
120+
} finally {
121+
window.event = prevEvent;
122+
}
123+
});
124+
}
125+
113126
describe('ReactDOMServerSelectiveHydration', () => {
114127
beforeEach(() => {
115128
jest.resetModuleRegistry();
@@ -125,6 +138,8 @@ describe('ReactDOMServerSelectiveHydration', () => {
125138
Suspense = React.Suspense;
126139

127140
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
141+
ContinuousEventPriority = require('react-reconciler/constants')
142+
.ContinuousEventPriority;
128143
});
129144

130145
it('hydrates the target boundary synchronously during a click', async () => {
@@ -1770,4 +1785,104 @@ describe('ReactDOMServerSelectiveHydration', () => {
17701785

17711786
document.body.removeChild(container);
17721787
});
1788+
1789+
it('can force hydration in response to sync update', () => {
1790+
function Child({text}) {
1791+
Scheduler.unstable_yieldValue(`Child ${text}`);
1792+
return <span ref={ref => (spanRef = ref)}>{text}</span>;
1793+
}
1794+
function App({text}) {
1795+
Scheduler.unstable_yieldValue(`App ${text}`);
1796+
return (
1797+
<div>
1798+
<Suspense fallback={null}>
1799+
<Child text={text} />
1800+
</Suspense>
1801+
</div>
1802+
);
1803+
}
1804+
1805+
let spanRef;
1806+
const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
1807+
expect(Scheduler).toHaveYielded(['App A', 'Child A']);
1808+
const container = document.createElement('div');
1809+
document.body.appendChild(container);
1810+
container.innerHTML = finalHTML;
1811+
const initialSpan = container.getElementsByTagName('span')[0];
1812+
const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
1813+
expect(Scheduler).toFlushUntilNextPaint(['App A']);
1814+
1815+
ReactDOM.flushSync(() => {
1816+
root.render(<App text="B" />);
1817+
});
1818+
expect(Scheduler).toHaveYielded(['App B', 'Child A', 'App B', 'Child B']);
1819+
expect(initialSpan).toBe(spanRef);
1820+
});
1821+
1822+
// @gate experimental || www
1823+
it('can force hydration in response to continuous update', () => {
1824+
function Child({text}) {
1825+
Scheduler.unstable_yieldValue(`Child ${text}`);
1826+
return <span ref={ref => (spanRef = ref)}>{text}</span>;
1827+
}
1828+
function App({text}) {
1829+
Scheduler.unstable_yieldValue(`App ${text}`);
1830+
return (
1831+
<div>
1832+
<Suspense fallback={null}>
1833+
<Child text={text} />
1834+
</Suspense>
1835+
</div>
1836+
);
1837+
}
1838+
1839+
let spanRef;
1840+
const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
1841+
expect(Scheduler).toHaveYielded(['App A', 'Child A']);
1842+
const container = document.createElement('div');
1843+
document.body.appendChild(container);
1844+
container.innerHTML = finalHTML;
1845+
const initialSpan = container.getElementsByTagName('span')[0];
1846+
const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
1847+
expect(Scheduler).toFlushUntilNextPaint(['App A']);
1848+
1849+
TODO_scheduleContinuousSchedulerTask(() => {
1850+
root.render(<App text="B" />);
1851+
});
1852+
expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']);
1853+
expect(initialSpan).toBe(spanRef);
1854+
});
1855+
1856+
it('can force hydration in response to default update', () => {
1857+
function Child({text}) {
1858+
Scheduler.unstable_yieldValue(`Child ${text}`);
1859+
return <span ref={ref => (spanRef = ref)}>{text}</span>;
1860+
}
1861+
function App({text}) {
1862+
Scheduler.unstable_yieldValue(`App ${text}`);
1863+
return (
1864+
<div>
1865+
<Suspense fallback={null}>
1866+
<Child text={text} />
1867+
</Suspense>
1868+
</div>
1869+
);
1870+
}
1871+
1872+
let spanRef;
1873+
const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
1874+
expect(Scheduler).toHaveYielded(['App A', 'Child A']);
1875+
const container = document.createElement('div');
1876+
document.body.appendChild(container);
1877+
container.innerHTML = finalHTML;
1878+
const initialSpan = container.getElementsByTagName('span')[0];
1879+
const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
1880+
expect(Scheduler).toFlushUntilNextPaint(['App A']);
1881+
1882+
ReactDOM.unstable_batchedUpdates(() => {
1883+
root.render(<App text="B" />);
1884+
});
1885+
expect(Scheduler).toFlushAndYield(['App B', 'Child A', 'App B', 'Child B']);
1886+
expect(initialSpan).toBe(spanRef);
1887+
});
17731888
});

packages/react-reconciler/src/ReactFiberLane.new.js

+45-37
Original file line numberDiff line numberDiff line change
@@ -36,39 +36,39 @@ export const TotalLanes = 31;
3636
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
3737
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
3838

39-
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
40-
41-
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
42-
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
43-
44-
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
45-
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
46-
47-
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
48-
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
49-
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
50-
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
51-
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
52-
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
53-
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
54-
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
55-
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
56-
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
57-
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
58-
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
59-
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
60-
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
61-
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
62-
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
63-
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
64-
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
65-
66-
const RetryLanes: Lanes = /* */ 0b0000111110000000000000000000000;
67-
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
68-
const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
69-
const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
70-
const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
71-
const RetryLane5: Lane = /* */ 0b0000100000000000000000000000000;
39+
export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
40+
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
41+
42+
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
43+
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
44+
45+
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
46+
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
47+
48+
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000;
49+
const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000;
50+
const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000;
51+
const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000;
52+
const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000;
53+
const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000;
54+
const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000;
55+
const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000;
56+
const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000;
57+
const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000;
58+
const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000;
59+
const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000;
60+
const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000;
61+
const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000;
62+
const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000;
63+
const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000;
64+
const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000;
65+
const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000;
66+
67+
const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000;
68+
const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000;
69+
const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000;
70+
const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000;
71+
const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000;
7272

7373
export const SomeRetryLane: Lane = RetryLane1;
7474

@@ -85,6 +85,9 @@ export const OffscreenLane: Lane = /* */ 0b1000000000000000000
8585
// It should be kept in sync with the Lanes values above.
8686
export function getLabelForLane(lane: Lane): string | void {
8787
if (enableSchedulingProfiler) {
88+
if (lane & SyncHydrationLane) {
89+
return 'SyncHydrationLane';
90+
}
8891
if (lane & SyncLane) {
8992
return 'Sync';
9093
}
@@ -131,6 +134,8 @@ let nextRetryLane: Lane = RetryLane1;
131134

132135
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
133136
switch (getHighestPriorityLane(lanes)) {
137+
case SyncHydrationLane:
138+
return SyncHydrationLane;
134139
case SyncLane:
135140
return SyncLane;
136141
case InputContinuousHydrationLane:
@@ -164,7 +169,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
164169
case RetryLane2:
165170
case RetryLane3:
166171
case RetryLane4:
167-
case RetryLane5:
168172
return lanes & RetryLanes;
169173
case SelectiveHydrationLane:
170174
return SelectiveHydrationLane;
@@ -327,6 +331,7 @@ export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
327331

328332
function computeExpirationTime(lane: Lane, currentTime: number) {
329333
switch (lane) {
334+
case SyncHydrationLane:
330335
case SyncLane:
331336
case InputContinuousHydrationLane:
332337
case InputContinuousLane:
@@ -364,7 +369,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
364369
case RetryLane2:
365370
case RetryLane3:
366371
case RetryLane4:
367-
case RetryLane5:
368372
// TODO: Retries should be allowed to expire if they are CPU bound for
369373
// too long, but when I made this change it caused a spike in browser
370374
// crashes. There must be some other underlying bug; not super urgent but
@@ -459,7 +463,7 @@ export function getLanesToRetrySynchronouslyOnError(
459463
}
460464

461465
export function includesSyncLane(lanes: Lanes): boolean {
462-
return (lanes & SyncLane) !== NoLanes;
466+
return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
463467
}
464468

465469
export function includesNonIdleWork(lanes: Lanes): boolean {
@@ -469,6 +473,8 @@ export function includesOnlyRetries(lanes: Lanes): boolean {
469473
return (lanes & RetryLanes) === lanes;
470474
}
471475
export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
476+
// TODO: Should hydration lanes be included here? This function is only
477+
// used in `updateDeferredValueImpl`.
472478
const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
473479
return (lanes & UrgentLanes) === NoLanes;
474480
}
@@ -749,6 +755,9 @@ export function getBumpedLaneForHydration(
749755

750756
let lane;
751757
switch (renderLane) {
758+
case SyncLane:
759+
lane = SyncHydrationLane;
760+
break;
752761
case InputContinuousLane:
753762
lane = InputContinuousHydrationLane;
754763
break;
@@ -775,7 +784,6 @@ export function getBumpedLaneForHydration(
775784
case RetryLane2:
776785
case RetryLane3:
777786
case RetryLane4:
778-
case RetryLane5:
779787
lane = TransitionHydrationLane;
780788
break;
781789
case IdleLane:

0 commit comments

Comments
 (0)