Skip to content

Commit 5379b61

Browse files
tyao1acdlite
andauthored
Batch sync, default and continuous lanes (#25700)
<!-- 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? --> This is the other approach for unifying default and sync lane #25524. The approach in that PR is to merge default and continuous lane into the sync lane, and use a new field to track the priority. But there are a couple places that field will be needed, and it is difficult to correctly reset the field when there is no sync lane. In this PR we take the other approach that doesn't remove any lane, but batch them to get the behavior we want. ## 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 Co-authored-by: Andrew Clark <hi@andrewclark.io>
1 parent bbf4d22 commit 5379b61

26 files changed

Lines changed: 414 additions & 267 deletions

‎packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,17 +275,32 @@ describe('ReactDOMFiberAsync', () => {
275275
expect(ops).toEqual([]);
276276
});
277277
// Only the active updates have flushed
278-
expect(container.textContent).toEqual('BC');
279-
expect(ops).toEqual(['BC']);
278+
if(gate(flags=>flags.enableUnifiedSyncLane)){
279+
expect(container.textContent).toEqual('ABC');
280+
expect(ops).toEqual(['ABC']);
281+
}else{
282+
expect(container.textContent).toEqual('BC');
283+
expect(ops).toEqual(['BC']);
284+
}
280285

281-
instance.push('D');
282-
expect(container.textContent).toEqual('BC');
283-
expect(ops).toEqual(['BC']);
286+
if(gate(flags=>flags.enableUnifiedSyncLane)){
287+
instance.push('D');
288+
expect(container.textContent).toEqual('ABC');
289+
expect(ops).toEqual(['ABC']);
290+
}else{
291+
instance.push('D');
292+
expect(container.textContent).toEqual('BC');
293+
expect(ops).toEqual(['BC']);
294+
}
284295

285296
// Flush the async updates
286297
Scheduler.unstable_flushAll();
287298
expect(container.textContent).toEqual('ABCD');
288-
expect(ops).toEqual(['BC','ABCD']);
299+
if(gate(flags=>flags.enableUnifiedSyncLane)){
300+
expect(ops).toEqual(['ABC','ABCD']);
301+
}else{
302+
expect(ops).toEqual(['BC','ABCD']);
303+
}
289304
});
290305

291306
// @gate www

‎packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js‎

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -449,10 +449,9 @@ describe('ReactDOMServerPartialHydration', () => {
449449
expect(deleted.length).toBe(0);
450450

451451
// Performing an update should force it to delete the boundary
452-
root.render(<Appvalue={true}/>);
453-
454-
Scheduler.unstable_flushAll();
455-
jest.runAllTimers();
452+
awaitact(async()=>{
453+
root.render(<Appvalue={true}/>);
454+
});
456455

457456
expect(hydrated.length).toBe(1);
458457
expect(deleted.length).toBe(1);
@@ -945,13 +944,12 @@ describe('ReactDOMServerPartialHydration', () => {
945944
root.render(<Apptext="Hi"className="hi"/>);
946945

947946
// At the same time, resolving the promise so that rendering can complete.
948-
suspend=false;
949-
resolve();
950-
awaitpromise;
951-
952947
// This should first complete the hydration and then flush the update onto the hydrated state.
953-
Scheduler.unstable_flushAll();
954-
jest.runAllTimers();
948+
awaitact(async()=>{
949+
suspend=false;
950+
resolve();
951+
awaitpromise;
952+
});
955953

956954
// The new span should be the same since we should have successfully hydrated
957955
// before changing it.
@@ -1093,9 +1091,9 @@ describe('ReactDOMServerPartialHydration', () => {
10931091
expect(ref.current).toBe(null);
10941092

10951093
// Render an update, but leave it still suspended.
1096-
root.render(<Apptext="Hi"className="hi"/>);
1097-
Scheduler.unstable_flushAll();
1098-
jest.runAllTimers();
1094+
awaitact(async()=>{
1095+
root.render(<Apptext="Hi"className="hi"/>);
1096+
});
10991097

11001098
// Flushing now should delete the existing content and show the fallback.
11011099

@@ -1104,12 +1102,11 @@ describe('ReactDOMServerPartialHydration', () => {
11041102
expect(container.textContent).toBe('Loading...');
11051103

11061104
// Unsuspending shows the content.
1107-
suspend=false;
1108-
resolve();
1109-
awaitpromise;
1110-
1111-
Scheduler.unstable_flushAll();
1112-
jest.runAllTimers();
1105+
awaitact(async()=>{
1106+
suspend=false;
1107+
resolve();
1108+
awaitpromise;
1109+
});
11131110

11141111
constspan=container.getElementsByTagName('span')[0];
11151112
expect(span.textContent).toBe('Hi');
@@ -1174,23 +1171,21 @@ describe('ReactDOMServerPartialHydration', () => {
11741171
expect(ref.current).toBe(span);
11751172

11761173
// Render an update, but leave it still suspended.
1177-
root.render(<Apptext="Hi"className="hi"/>);
1178-
11791174
// Flushing now should delete the existing content and show the fallback.
1180-
Scheduler.unstable_flushAll();
1181-
jest.runAllTimers();
1175+
awaitact(async()=>{
1176+
root.render(<Apptext="Hi"className="hi"/>);
1177+
});
11821178

11831179
expect(container.getElementsByTagName('span').length).toBe(1);
11841180
expect(ref.current).toBe(span);
11851181
expect(container.textContent).toBe('');
11861182

11871183
// Unsuspending shows the content.
1188-
suspend=false;
1189-
resolve();
1190-
awaitpromise;
1191-
1192-
Scheduler.unstable_flushAll();
1193-
jest.runAllTimers();
1184+
awaitact(async()=>{
1185+
suspend=false;
1186+
resolve();
1187+
awaitpromise;
1188+
});
11941189

11951190
expect(span.textContent).toBe('Hi');
11961191
expect(span.className).toBe('hi');
@@ -1252,20 +1247,21 @@ describe('ReactDOMServerPartialHydration', () => {
12521247
expect(ref.current).toBe(null);
12531248

12541249
// Render an update, but leave it still suspended.
1255-
root.render(<Apptext="Hi"className="hi"/>);
1256-
12571250
// Flushing now should delete the existing content and show the fallback.
1258-
Scheduler.unstable_flushAll();
1259-
jest.runAllTimers();
1251+
awaitact(async()=>{
1252+
root.render(<Apptext="Hi"className="hi"/>);
1253+
});
12601254

12611255
expect(container.getElementsByTagName('span').length).toBe(0);
12621256
expect(ref.current).toBe(null);
12631257
expect(container.textContent).toBe('Loading...');
12641258

12651259
// Unsuspending shows the content.
1266-
suspend=false;
1267-
resolve();
1268-
awaitpromise;
1260+
awaitact(async()=>{
1261+
suspend=false;
1262+
resolve();
1263+
awaitpromise;
1264+
});
12691265

12701266
Scheduler.unstable_flushAll();
12711267
jest.runAllTimers();
@@ -1490,13 +1486,12 @@ describe('ReactDOMServerPartialHydration', () => {
14901486
);
14911487

14921488
// At the same time, resolving the promise so that rendering can complete.
1493-
suspend=false;
1494-
resolve();
1495-
awaitpromise;
1496-
14971489
// This should first complete the hydration and then flush the update onto the hydrated state.
1498-
Scheduler.unstable_flushAll();
1499-
jest.runAllTimers();
1490+
awaitact(async()=>{
1491+
suspend=false;
1492+
resolve();
1493+
awaitpromise;
1494+
});
15001495

15011496
// Since this should have been hydrated, this should still be the same span.
15021497
constnewSpan=container.getElementsByTagName('span')[0];
@@ -1569,27 +1564,25 @@ describe('ReactDOMServerPartialHydration', () => {
15691564
expect(ref.current).toBe(null);
15701565

15711566
// Render an update, but leave it still suspended.
1572-
root.render(
1573-
<Context.Providervalue={{text: 'Hi',className: 'hi'}}>
1574-
<App/>
1575-
</Context.Provider>,
1576-
);
1577-
15781567
// Flushing now should delete the existing content and show the fallback.
1579-
Scheduler.unstable_flushAll();
1580-
jest.runAllTimers();
1568+
awaitact(async()=>{
1569+
root.render(
1570+
<Context.Providervalue={{text: 'Hi',className: 'hi'}}>
1571+
<App/>
1572+
</Context.Provider>,
1573+
);
1574+
});
15811575

15821576
expect(container.getElementsByTagName('span').length).toBe(0);
15831577
expect(ref.current).toBe(null);
15841578
expect(container.textContent).toBe('Loading...');
15851579

15861580
// Unsuspending shows the content.
1587-
suspend=false;
1588-
resolve();
1589-
awaitpromise;
1590-
1591-
Scheduler.unstable_flushAll();
1592-
jest.runAllTimers();
1581+
awaitact(async()=>{
1582+
suspend=false;
1583+
resolve();
1584+
awaitpromise;
1585+
});
15931586

15941587
constspan=container.getElementsByTagName('span')[0];
15951588
expect(span.textContent).toBe('Hi');
@@ -2320,16 +2313,15 @@ describe('ReactDOMServerPartialHydration', () => {
23202313

23212314
// Render an update, which will be higher or the same priority as pinging the hydration.
23222315
// The new update doesn't suspend.
2323-
root.render(
2324-
<ClassName.Providervalue={'hi'}>
2325-
<Apptext="Hi"/>
2326-
</ClassName.Provider>,
2327-
);
2328-
23292316
// Since we're still suspended on the original data, we can't hydrate.
23302317
// This will force all expiration times to flush.
2331-
Scheduler.unstable_flushAll();
2332-
jest.runAllTimers();
2318+
awaitact(async()=>{
2319+
root.render(
2320+
<ClassName.Providervalue={'hi'}>
2321+
<Apptext="Hi"/>
2322+
</ClassName.Provider>,
2323+
);
2324+
});
23332325

23342326
// This will now be a new span because we weren't able to hydrate before
23352327
constnewSpan=container.getElementsByTagName('span')[0];

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,7 +1786,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
17861786
document.body.removeChild(container);
17871787
});
17881788

1789-
it('can force hydration in response to sync update',()=>{
1789+
it('can force hydration in response to sync update',async()=>{
17901790
functionChild({text}){
17911791
Scheduler.unstable_yieldValue(`Child ${text}`);
17921792
return<spanref={ref=>(spanRef=ref)}>{text}</span>;
@@ -1812,15 +1812,17 @@ describe('ReactDOMServerSelectiveHydration', () => {
18121812
constroot=ReactDOMClient.hydrateRoot(container,<Apptext="A"/>);
18131813
expect(Scheduler).toFlushUntilNextPaint(['App A']);
18141814

1815-
ReactDOM.flushSync(()=>{
1816-
root.render(<Apptext="B"/>);
1815+
awaitact(async()=>{
1816+
ReactDOM.flushSync(()=>{
1817+
root.render(<Apptext="B"/>);
1818+
});
18171819
});
18181820
expect(Scheduler).toHaveYielded(['App B','Child A','App B','Child B']);
18191821
expect(initialSpan).toBe(spanRef);
18201822
});
18211823

18221824
// @gate experimental || www
1823-
it('can force hydration in response to continuous update',()=>{
1825+
it('can force hydration in response to continuous update',async()=>{
18241826
functionChild({text}){
18251827
Scheduler.unstable_yieldValue(`Child ${text}`);
18261828
return<spanref={ref=>(spanRef=ref)}>{text}</span>;
@@ -1846,14 +1848,17 @@ describe('ReactDOMServerSelectiveHydration', () => {
18461848
constroot=ReactDOMClient.hydrateRoot(container,<Apptext="A"/>);
18471849
expect(Scheduler).toFlushUntilNextPaint(['App A']);
18481850

1849-
TODO_scheduleContinuousSchedulerTask(()=>{
1850-
root.render(<Apptext="B"/>);
1851+
awaitact(async()=>{
1852+
TODO_scheduleContinuousSchedulerTask(()=>{
1853+
root.render(<Apptext="B"/>);
1854+
});
18511855
});
1852-
expect(Scheduler).toFlushAndYield(['App B','Child A','App B','Child B']);
1856+
1857+
expect(Scheduler).toHaveYielded(['App B','Child A','App B','Child B']);
18531858
expect(initialSpan).toBe(spanRef);
18541859
});
18551860

1856-
it('can force hydration in response to default update',()=>{
1861+
it('can force hydration in response to default update',async()=>{
18571862
functionChild({text}){
18581863
Scheduler.unstable_yieldValue(`Child ${text}`);
18591864
return<spanref={ref=>(spanRef=ref)}>{text}</span>;
@@ -1878,11 +1883,10 @@ describe('ReactDOMServerSelectiveHydration', () => {
18781883
constinitialSpan=container.getElementsByTagName('span')[0];
18791884
constroot=ReactDOMClient.hydrateRoot(container,<Apptext="A"/>);
18801885
expect(Scheduler).toFlushUntilNextPaint(['App A']);
1881-
1882-
ReactDOM.unstable_batchedUpdates(()=>{
1886+
awaitact(async()=>{
18831887
root.render(<Apptext="B"/>);
18841888
});
1885-
expect(Scheduler).toFlushAndYield(['App B','Child A','App B','Child B']);
1889+
expect(Scheduler).toHaveYielded(['App B','Child A','App B','Child B']);
18861890
expect(initialSpan).toBe(spanRef);
18871891
});
18881892

0 commit comments

Comments
 (0)