@@ -166,7 +166,8 @@ type KeyNode = [
166166
167167const REPLAY_NODE = 0 ;
168168const REPLAY_SUSPENSE_BOUNDARY = 1 ;
169- const RESUME_SEGMENT = 2 ;
169+ const RESUME_ELEMENT = 2 ;
170+ const RESUME_SLOT = 3 ;
170171
171172type ResumableParentNode =
172173 | [
@@ -185,10 +186,16 @@ type ResumableParentNode =
185186type ResumableNode =
186187 | ResumableParentNode
187188 | [
188- 2 , // RESUME_SEGMENT
189- number /* index */ ,
189+ 2 , // RESUME_ELEMENT
190+ string | null /* name */ ,
191+ string | number /* key */ ,
190192 number /* segment id */ ,
191- ] ;
193+ ]
194+ | [
195+ 3 , // RESUME_SLOT
196+ number /* index */ ,
197+ number /* segment id */ ,
198+ ] ;
192199
193200type PostponedHoles = {
194201 workingMap : Map < KeyNode , ResumableParentNode> ,
@@ -784,7 +791,7 @@ function renderSuspenseBoundary(
784791 }
785792 try {
786793 // 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 ) ;
788795 pushSegmentFinale (
789796 contentRootSegment . chunks ,
790797 request . renderState ,
@@ -873,7 +880,7 @@ function renderBackupSuspenseBoundary(
873880 const segment = task . blockedSegment ;
874881
875882 pushStartCompletedSuspenseBoundary ( segment . chunks ) ;
876- renderNode ( request , task , content , 0 ) ;
883+ renderNode ( request , task , content , - 1 ) ;
877884 pushEndCompletedSuspenseBoundary ( segment . chunks ) ;
878885
879886 popComponentStackInDEV ( task ) ;
@@ -903,7 +910,7 @@ function renderHostElement(
903910
904911 // We use the non-destructive form because if something suspends, we still
905912 // need to pop back up and finish this subtree of HTML.
906- renderNode ( request , task , children , 0 ) ;
913+ renderNode ( request , task , children , - 1 ) ;
907914
908915 // We expect that errors will fatal the whole task and that we don't need
909916 // the correct context. Therefore this is not in a finally.
@@ -970,13 +977,13 @@ function finishClassComponent(
970977 childContextTypes ,
971978 ) ;
972979 task . legacyContext = mergedContext ;
973- renderNodeDestructive ( request , task , null , nextChildren , 0 ) ;
980+ renderNodeDestructive ( request , task , null , nextChildren , - 1 ) ;
974981 task . legacyContext = previousContext ;
975982 return ;
976983 }
977984 }
978985
979- renderNodeDestructive ( request , task , null , nextChildren , 0 ) ;
986+ renderNodeDestructive ( request , task , null , nextChildren , - 1 ) ;
980987}
981988
982989function renderClassComponent (
@@ -1170,20 +1177,20 @@ function finishFunctionComponent(
11701177 // Modify the id context. Because we'll need to reset this if something
11711178 // suspends or errors, we'll use the non-destructive render path.
11721179 task . treeContext = pushTreeContext ( prevTreeContext , totalChildren , index ) ;
1173- renderNode ( request , task , children , 0 ) ;
1180+ renderNode ( request , task , children , - 1 ) ;
11741181 // Like the other contexts, this does not need to be in a finally block
11751182 // because renderNode takes care of unwinding the stack.
11761183 task . treeContext = prevTreeContext ;
11771184 } else if ( didEmitFormStateMarkers ) {
11781185 // If there were formState hooks, we must use the non-destructive path
11791186 // because this component is not a pure indirection; we emitted markers
11801187 // to the stream.
1181- renderNode ( request , task , children , 0 ) ;
1188+ renderNode ( request , task , children , - 1 ) ;
11821189 } else {
11831190 // We're now successfully past this task, and we haven't modified the
11841191 // context stack. We don't have to pop back to the previous task every
11851192 // again, so we can use the destructive recursive form.
1186- renderNodeDestructive ( request , task , null , children , 0 ) ;
1193+ renderNodeDestructive ( request , task , null , children , - 1 ) ;
11871194 }
11881195}
11891196
@@ -1353,7 +1360,7 @@ function renderContextConsumer(
13531360 const newValue = readContext ( context ) ;
13541361 const newChildren = render ( newValue ) ;
13551362
1356- renderNodeDestructive ( request , task , null , newChildren , 0 ) ;
1363+ renderNodeDestructive ( request , task , null , newChildren , - 1 ) ;
13571364}
13581365
13591366function renderContextProvider (
@@ -1370,7 +1377,7 @@ function renderContextProvider(
13701377 prevSnapshot = task . context ;
13711378 }
13721379 task . context = pushProvider ( context , value ) ;
1373- renderNodeDestructive ( request , task , null , children , 0 ) ;
1380+ renderNodeDestructive ( request , task , null , children , - 1 ) ;
13741381 task . context = popProvider ( context ) ;
13751382 if ( __DEV__ ) {
13761383 if ( prevSnapshot !== task . context ) {
@@ -1413,7 +1420,7 @@ function renderOffscreen(request: Request, task: Task, props: Object): void {
14131420 } else {
14141421 // A visible Offscreen boundary is treated exactly like a fragment: a
14151422 // pure indirection.
1416- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1423+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
14171424 }
14181425}
14191426
@@ -1460,7 +1467,7 @@ function renderElement(
14601467 case REACT_STRICT_MODE_TYPE :
14611468 case REACT_PROFILER_TYPE :
14621469 case REACT_FRAGMENT_TYPE : {
1463- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1470+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
14641471 return ;
14651472 }
14661473 case REACT_OFFSCREEN_TYPE : {
@@ -1470,13 +1477,13 @@ function renderElement(
14701477 case REACT_SUSPENSE_LIST_TYPE : {
14711478 pushBuiltInComponentStackInDEV ( task , 'SuspenseList' ) ;
14721479 // TODO: SuspenseList should control the boundaries.
1473- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1480+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
14741481 popComponentStackInDEV ( task ) ;
14751482 return ;
14761483 }
14771484 case REACT_SCOPE_TYPE : {
14781485 if ( enableScopeAPI ) {
1479- renderNodeDestructive ( request , task , null , props . children , 0 ) ;
1486+ renderNodeDestructive ( request , task , null , props . children , - 1 ) ;
14801487 return ;
14811488 }
14821489 throw new Error ( 'ReactDOMServer does not yet support scope components.' ) ;
@@ -1645,7 +1652,11 @@ function renderNodeDestructiveImpl(
16451652 const ref = element . ref ;
16461653 const name = getComponentNameFromType ( type ) ;
16471654 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+ ] ;
16491660 renderElement ( request , task , prevThenableState , type , props , ref ) ;
16501661 task . keyPath = prevKeyPath ;
16511662 return ;
@@ -1805,61 +1816,29 @@ function renderChildrenArray(
18051816 children : Array < any > ,
18061817 childIndex : number ,
18071818) {
1819+ const prevKeyPath = task . keyPath ;
1820+ if ( childIndex !== - 1 ) {
1821+ task . keyPath = [ task . keyPath , '' , childIndex ] ;
1822+ }
18081823 const prevTreeContext = task . treeContext ;
18091824 const totalChildren = children . length ;
18101825 for ( let i = 0 ; i < totalChildren ; i ++ ) {
18111826 const node = children [ i ] ;
18121827 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-
18491828 // We need to use the non-destructive form so that we can safely pop back
18501829 // up and render the sibling if something suspends.
18511830 renderNode ( request , task , node , i ) ;
18521831 }
18531832 // Because this context is always set right before rendering every child, we
18541833 // only need to reset it to the previous value at the very end.
18551834 task . treeContext = prevTreeContext ;
1835+ task . keyPath = prevKeyPath ;
18561836}
18571837
18581838function trackPostpone (
18591839 request : Request ,
18601840 trackedPostpones : PostponedHoles ,
18611841 task : Task ,
1862- childIndex : number ,
18631842 segment : Segment ,
18641843) : void {
18651844 segment . status = POSTPONED ;
@@ -1901,8 +1880,20 @@ function trackPostpone(
19011880 ) ;
19021881 }
19031882
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_SLOT , task . childIndex , segment . id ] ;
1895+ addToResumableParent ( resumableNode , keyPath , trackedPostpones ) ;
1896+ }
19061897}
19071898
19081899function injectPostponedHole (
@@ -2060,13 +2051,7 @@ function renderNode(
20602051 task ,
20612052 postponeInstance . message ,
20622053 ) ;
2063- trackPostpone (
2064- request ,
2065- trackedPostpones ,
2066- task ,
2067- childIndex ,
2068- postponedSegment ,
2069- ) ;
2054+ trackPostpone ( request , trackedPostpones , task , postponedSegment ) ;
20702055
20712056 // Restore the context. We assume that this will be restored by the inner
20722057 // functions in case nothing throws so we don't use "finally" here.
@@ -2414,13 +2399,7 @@ function retryTask(request: Request, task: Task): void {
24142399 task . abortSet . delete ( task ) ;
24152400 const postponeInstance : Postpone = ( x : any ) ;
24162401 logPostpone ( request , postponeInstance . message ) ;
2417- trackPostpone (
2418- request ,
2419- trackedPostpones ,
2420- task ,
2421- task . childIndex ,
2422- segment ,
2423- ) ;
2402+ trackPostpone ( request , trackedPostpones , task , segment ) ;
24242403 finishedTask ( request , task . blockedBoundary , segment ) ;
24252404 return ;
24262405 }
0 commit comments