@@ -166,7 +166,8 @@ type KeyNode = [
166
166
167
167
const REPLAY_NODE = 0 ;
168
168
const REPLAY_SUSPENSE_BOUNDARY = 1 ;
169
- const RESUME_SEGMENT = 2 ;
169
+ const RESUME_NODE = 2 ;
170
+ const RESUME_ELEMENT = 3 ;
170
171
171
172
type ResumableParentNode =
172
173
| [
@@ -185,9 +186,15 @@ type ResumableParentNode =
185
186
type ResumableNode =
186
187
| ResumableParentNode
187
188
| [
188
- 2 , // RESUME_SEGMENT
189
+ 2 , // RESUME_NODE
189
190
number /* index */ ,
190
191
number /* segment id */ ,
192
+ ]
193
+ | [
194
+ 3 , // RESUME_ELEMENT
195
+ string | null /* name */ ,
196
+ string | number /* key */ ,
197
+ number /* segment id */ ,
191
198
] ;
192
199
193
200
type PostponedHoles = {
@@ -784,7 +791,7 @@ function renderSuspenseBoundary(
784
791
}
785
792
try {
786
793
// We use the safe form because we don't handle suspending here. Only error handling.
787
- renderNode ( request , task , content , 0 ) ;
794
+ renderNode ( request , task , content , - 1 ) ;
788
795
pushSegmentFinale (
789
796
contentRootSegment . chunks ,
790
797
request . renderState ,
@@ -873,7 +880,7 @@ function renderBackupSuspenseBoundary(
873
880
const segment = task . blockedSegment ;
874
881
875
882
pushStartCompletedSuspenseBoundary ( segment . chunks ) ;
876
- renderNode ( request , task , content , 0 ) ;
883
+ renderNode ( request , task , content , - 1 ) ;
877
884
pushEndCompletedSuspenseBoundary ( segment . chunks ) ;
878
885
879
886
popComponentStackInDEV ( task ) ;
@@ -903,7 +910,7 @@ function renderHostElement(
903
910
904
911
// We use the non-destructive form because if something suspends, we still
905
912
// need to pop back up and finish this subtree of HTML.
906
- renderNode ( request , task , children , 0 ) ;
913
+ renderNode ( request , task , children , - 1 ) ;
907
914
908
915
// We expect that errors will fatal the whole task and that we don't need
909
916
// the correct context. Therefore this is not in a finally.
@@ -970,13 +977,13 @@ function finishClassComponent(
970
977
childContextTypes ,
971
978
) ;
972
979
task . legacyContext = mergedContext ;
973
- renderNodeDestructive ( request , task , null , nextChildren , 0 ) ;
980
+ renderNodeDestructive ( request , task , null , nextChildren , - 1 ) ;
974
981
task . legacyContext = previousContext ;
975
982
return ;
976
983
}
977
984
}
978
985
979
- renderNodeDestructive ( request , task , null , nextChildren , 0 ) ;
986
+ renderNodeDestructive ( request , task , null , nextChildren , - 1 ) ;
980
987
}
981
988
982
989
function renderClassComponent (
@@ -1170,20 +1177,20 @@ function finishFunctionComponent(
1170
1177
// Modify the id context. Because we'll need to reset this if something
1171
1178
// suspends or errors, we'll use the non-destructive render path.
1172
1179
task . treeContext = pushTreeContext ( prevTreeContext , totalChildren , index ) ;
1173
- renderNode ( request , task , children , 0 ) ;
1180
+ renderNode ( request , task , children , - 1 ) ;
1174
1181
// Like the other contexts, this does not need to be in a finally block
1175
1182
// because renderNode takes care of unwinding the stack.
1176
1183
task . treeContext = prevTreeContext ;
1177
1184
} else if ( didEmitFormStateMarkers ) {
1178
1185
// If there were formState hooks, we must use the non-destructive path
1179
1186
// because this component is not a pure indirection; we emitted markers
1180
1187
// to the stream.
1181
- renderNode ( request , task , children , 0 ) ;
1188
+ renderNode ( request , task , children , - 1 ) ;
1182
1189
} else {
1183
1190
// We're now successfully past this task, and we haven't modified the
1184
1191
// context stack. We don't have to pop back to the previous task every
1185
1192
// again, so we can use the destructive recursive form.
1186
- renderNodeDestructive ( request , task , null , children , 0 ) ;
1193
+ renderNodeDestructive ( request , task , null , children , - 1 ) ;
1187
1194
}
1188
1195
}
1189
1196
@@ -1353,7 +1360,7 @@ function renderContextConsumer(
1353
1360
const newValue = readContext ( context ) ;
1354
1361
const newChildren = render ( newValue ) ;
1355
1362
1356
- renderNodeDestructive ( request , task , null , newChildren , 0 ) ;
1363
+ renderNodeDestructive ( request , task , null , newChildren , - 1 ) ;
1357
1364
}
1358
1365
1359
1366
function renderContextProvider (
@@ -1370,7 +1377,7 @@ function renderContextProvider(
1370
1377
prevSnapshot = task . context ;
1371
1378
}
1372
1379
task . context = pushProvider ( context , value ) ;
1373
- renderNodeDestructive ( request , task , null , children , 0 ) ;
1380
+ renderNodeDestructive ( request , task , null , children , - 1 ) ;
1374
1381
task . context = popProvider ( context ) ;
1375
1382
if ( __DEV__ ) {
1376
1383
if ( prevSnapshot !== task . context ) {
@@ -1413,7 +1420,7 @@ function renderOffscreen(request: Request, task: Task, props: Object): void {
1413
1420
} else {
1414
1421
// A visible Offscreen boundary is treated exactly like a fragment: a
1415
1422
// pure indirection.
1416
- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1423
+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
1417
1424
}
1418
1425
}
1419
1426
@@ -1460,7 +1467,7 @@ function renderElement(
1460
1467
case REACT_STRICT_MODE_TYPE :
1461
1468
case REACT_PROFILER_TYPE :
1462
1469
case REACT_FRAGMENT_TYPE : {
1463
- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1470
+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
1464
1471
return ;
1465
1472
}
1466
1473
case REACT_OFFSCREEN_TYPE : {
@@ -1470,13 +1477,13 @@ function renderElement(
1470
1477
case REACT_SUSPENSE_LIST_TYPE : {
1471
1478
pushBuiltInComponentStackInDEV ( task , 'SuspenseList' ) ;
1472
1479
// TODO: SuspenseList should control the boundaries.
1473
- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1480
+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
1474
1481
popComponentStackInDEV ( task ) ;
1475
1482
return ;
1476
1483
}
1477
1484
case REACT_SCOPE_TYPE : {
1478
1485
if ( enableScopeAPI ) {
1479
- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1486
+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
1480
1487
return ;
1481
1488
}
1482
1489
throw new Error ( 'ReactDOMServer does not yet support scope components.' ) ;
@@ -1645,7 +1652,11 @@ function renderNodeDestructiveImpl(
1645
1652
const ref = element . ref ;
1646
1653
const name = getComponentNameFromType ( type ) ;
1647
1654
const prevKeyPath = task . keyPath ;
1648
- task . keyPath = [ task . keyPath , name , key == null ? childIndex : key ] ;
1655
+ task . keyPath = [
1656
+ task . keyPath ,
1657
+ name ,
1658
+ key == null ? ( childIndex === - 1 ? 0 : childIndex ) : key ,
1659
+ ] ;
1649
1660
renderElement ( request , task , prevThenableState , type , props , ref ) ;
1650
1661
task . keyPath = prevKeyPath ;
1651
1662
return ;
@@ -1805,61 +1816,29 @@ function renderChildrenArray(
1805
1816
children : Array < any > ,
1806
1817
childIndex : number ,
1807
1818
) {
1819
+ const prevKeyPath = task . keyPath ;
1820
+ if ( childIndex !== - 1 ) {
1821
+ task . keyPath = [ task . keyPath , '' , childIndex ] ;
1822
+ }
1808
1823
const prevTreeContext = task . treeContext ;
1809
1824
const totalChildren = children . length ;
1810
1825
for ( let i = 0 ; i < totalChildren ; i ++ ) {
1811
1826
const node = children [ i ] ;
1812
1827
task . treeContext = pushTreeContext ( prevTreeContext , totalChildren , i ) ;
1813
-
1814
- // Nested arrays behave like a "fragment node" which is keyed.
1815
- // Therefore we need to add the current index as a parent key.
1816
- // We first check if the nested nodes are arrays or iterables.
1817
-
1818
- if ( isArray ( node ) ) {
1819
- const prevKeyPath = task . keyPath ;
1820
- task . keyPath = [ task . keyPath , '' , childIndex ] ;
1821
- renderChildrenArray ( request , task , node , i ) ;
1822
- task . keyPath = prevKeyPath ;
1823
- continue ;
1824
- }
1825
-
1826
- const iteratorFn = getIteratorFn ( node ) ;
1827
- if ( iteratorFn ) {
1828
- if ( __DEV__ ) {
1829
- validateIterable ( node , iteratorFn ) ;
1830
- }
1831
- const iterator = iteratorFn . call ( node ) ;
1832
- if ( iterator ) {
1833
- let step = iterator . next ( ) ;
1834
- if ( ! step . done ) {
1835
- const prevKeyPath = task . keyPath ;
1836
- task . keyPath = [ task . keyPath , '' , childIndex ] ;
1837
- const nestedChildren = [ ] ;
1838
- do {
1839
- nestedChildren . push ( step . value ) ;
1840
- step = iterator . next ( ) ;
1841
- } while ( ! step . done ) ;
1842
- renderChildrenArray ( request , task , nestedChildren , i ) ;
1843
- task . keyPath = prevKeyPath ;
1844
- }
1845
- continue ;
1846
- }
1847
- }
1848
-
1849
1828
// We need to use the non-destructive form so that we can safely pop back
1850
1829
// up and render the sibling if something suspends.
1851
1830
renderNode ( request , task , node , i ) ;
1852
1831
}
1853
1832
// Because this context is always set right before rendering every child, we
1854
1833
// only need to reset it to the previous value at the very end.
1855
1834
task . treeContext = prevTreeContext ;
1835
+ task . keyPath = prevKeyPath ;
1856
1836
}
1857
1837
1858
1838
function trackPostpone (
1859
1839
request : Request ,
1860
1840
trackedPostpones : PostponedHoles ,
1861
1841
task : Task ,
1862
- childIndex : number ,
1863
1842
segment : Segment ,
1864
1843
) : void {
1865
1844
segment . status = POSTPONED ;
@@ -1901,8 +1880,20 @@ function trackPostpone(
1901
1880
) ;
1902
1881
}
1903
1882
1904
- const segmentNode : ResumableNode = [ RESUME_SEGMENT , childIndex , segment . id ] ;
1905
- addToResumableParent ( segmentNode , keyPath , trackedPostpones ) ;
1883
+ if ( task . childIndex === - 1 ) {
1884
+ // Resume at the position before the first array
1885
+ const resumableElement = [
1886
+ RESUME_ELEMENT ,
1887
+ keyPath [ 1 ] ,
1888
+ keyPath [ 2 ] ,
1889
+ segment . id ,
1890
+ ] ;
1891
+ addToResumableParent ( resumableElement , keyPath [ 0 ] , trackedPostpones ) ;
1892
+ } else {
1893
+ // Resume at the slot within the array
1894
+ const resumableNode = [ RESUME_NODE , task . childIndex , segment . id ] ;
1895
+ addToResumableParent ( resumableNode , keyPath , trackedPostpones ) ;
1896
+ }
1906
1897
}
1907
1898
1908
1899
function injectPostponedHole (
@@ -2060,13 +2051,7 @@ function renderNode(
2060
2051
task ,
2061
2052
postponeInstance . message ,
2062
2053
) ;
2063
- trackPostpone (
2064
- request ,
2065
- trackedPostpones ,
2066
- task ,
2067
- childIndex ,
2068
- postponedSegment ,
2069
- ) ;
2054
+ trackPostpone ( request , trackedPostpones , task , postponedSegment ) ;
2070
2055
2071
2056
// Restore the context. We assume that this will be restored by the inner
2072
2057
// functions in case nothing throws so we don't use "finally" here.
@@ -2414,13 +2399,7 @@ function retryTask(request: Request, task: Task): void {
2414
2399
task . abortSet . delete ( task ) ;
2415
2400
const postponeInstance : Postpone = ( x : any ) ;
2416
2401
logPostpone ( request , postponeInstance . message ) ;
2417
- trackPostpone (
2418
- request ,
2419
- trackedPostpones ,
2420
- task ,
2421
- task . childIndex ,
2422
- segment ,
2423
- ) ;
2402
+ trackPostpone ( request , trackedPostpones , task , segment ) ;
2424
2403
finishedTask ( request , task . blockedBoundary , segment ) ;
2425
2404
return ;
2426
2405
}
0 commit comments