@@ -17,6 +17,7 @@ import type {
17
17
Props ,
18
18
Container ,
19
19
ChildSet ,
20
+ Resource ,
20
21
} from './ReactFiberHostConfig' ;
21
22
import type {
22
23
SuspenseState ,
@@ -112,6 +113,7 @@ import {
112
113
maySuspendCommit ,
113
114
mayResourceSuspendCommit ,
114
115
preloadInstance ,
116
+ preloadResource ,
115
117
} from './ReactFiberHostConfig' ;
116
118
import {
117
119
getRootHostContainer ,
@@ -511,6 +513,10 @@ function updateHostComponent(
511
513
}
512
514
}
513
515
516
+ // This function must be called at the very end of the complete phase, because
517
+ // it might throw to suspend, and if the resource immediately loads, the work
518
+ // loop will resume rendering as if the work-in-progress completed. So it must
519
+ // fully complete.
514
520
// TODO: This should ideally move to begin phase, but currently the instance is
515
521
// not created until the complete phase. For our existing use cases, host nodes
516
522
// that suspend don't have children, so it doesn't matter. But that might not
@@ -521,13 +527,35 @@ function preloadInstanceAndSuspendIfNeeded(
521
527
props : Props ,
522
528
renderLanes : Lanes ,
523
529
) {
530
+ if ( ! maySuspendCommit ( type , props ) ) {
531
+ // If this flag was set previously, we can remove it. The flag
532
+ // represents whether this particular set of props might ever need to
533
+ // suspend. The safest thing to do is for maySuspendCommit to always
534
+ // return true, but if the renderer is reasonably confident that the
535
+ // underlying resource won't be evicted, it can return false as a
536
+ // performance optimization.
537
+ workInProgress . flags &= ~ SuspenseyCommit ;
538
+ return ;
539
+ }
540
+
541
+ // Mark this fiber with a flag. This gets set on all host instances
542
+ // that might possibly suspend, even if they don't need to suspend
543
+ // currently. We use this when revealing a prerendered tree, because
544
+ // even though the tree has "mounted", its resources might not have
545
+ // loaded yet.
524
546
workInProgress . flags |= SuspenseyCommit ;
547
+
525
548
// Check if we're rendering at a "non-urgent" priority. This is the same
526
549
// check that `useDeferredValue` does to determine whether it needs to
527
550
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
528
551
// suspending until you opt in with startTransition or Suspense) but it
529
552
// also happens to be the desired behavior for the concrete use cases we've
530
553
// thought of so far, like CSS loading, fonts, images, etc.
554
+ //
555
+ // We check the "root" render lanes here rather than the "subtree" render
556
+ // because during a retry or offscreen prerender, the "subtree" render
557
+ // lanes may include additional "base" lanes that were deferred during
558
+ // a previous render.
531
559
// TODO: We may decide to expose a way to force a fallback even during a
532
560
// sync update.
533
561
if ( ! includesOnlyNonUrgentLanes ( renderLanes ) ) {
@@ -553,6 +581,35 @@ function preloadInstanceAndSuspendIfNeeded(
553
581
}
554
582
}
555
583
584
+ function preloadResourceAndSuspendIfNeeded (
585
+ workInProgress : Fiber ,
586
+ resource : Resource ,
587
+ type : Type ,
588
+ props : Props ,
589
+ renderLanes : Lanes ,
590
+ ) {
591
+ // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
592
+ if ( ! mayResourceSuspendCommit ( resource ) ) {
593
+ workInProgress . flags &= ~ SuspenseyCommit ;
594
+ return ;
595
+ }
596
+
597
+ workInProgress . flags |= SuspenseyCommit ;
598
+
599
+ if ( ! includesOnlyNonUrgentLanes ( renderLanes ) ) {
600
+ // This is an urgent render. Don't suspend or show a fallback.
601
+ } else {
602
+ const isReady = preloadResource ( resource ) ;
603
+ if ( ! isReady ) {
604
+ if ( shouldRemainOnPreviousScreen ( ) ) {
605
+ // It's OK to suspend. Continue rendering.
606
+ } else {
607
+ suspendCommit ( ) ;
608
+ }
609
+ }
610
+ }
611
+ }
612
+
556
613
function scheduleRetryEffect (
557
614
workInProgress : Fiber ,
558
615
retryQueue : RetryQueue | null ,
@@ -1015,64 +1072,99 @@ function completeWork(
1015
1072
}
1016
1073
case HostHoistable : {
1017
1074
if ( enableFloat && supportsResources ) {
1018
- const currentRef = current ? current . ref : null ;
1019
- if ( currentRef !== workInProgress . ref ) {
1020
- markRef ( workInProgress ) ;
1021
- }
1022
-
1023
- let maySuspend = false ;
1075
+ // The branching here is more complicated than you might expect because
1076
+ // a HostHoistable sometimes corresponds to a Resource and sometimes
1077
+ // corresponds to an Instance. It can also switch during an update.
1024
1078
1025
- // @TODO refactor this block to create the instance here in complete phase if we
1026
- // are not hydrating.
1027
- if (
1079
+ const type = workInProgress . type ;
1080
+ const nextResource : Resource | null = workInProgress . memoizedState ;
1081
+ if ( current === null ) {
1028
1082
// We are mounting and must Update this Hoistable in this commit
1029
- current === null ||
1030
- // We are transitioning to, from, or between Hoistable Resources
1031
- // and require an update
1032
- current . memoizedState !== workInProgress . memoizedState
1033
- ) {
1034
- if ( workInProgress . memoizedState !== null ) {
1035
- maySuspend = mayResourceSuspendCommit ( workInProgress . memoizedState ) ;
1083
+ // @TODO refactor this block to create the instance here in complete
1084
+ // phase if we are not hydrating.
1085
+ markUpdate ( workInProgress ) ;
1086
+ if ( workInProgress . ref !== null ) {
1087
+ markRef ( workInProgress ) ;
1088
+ }
1089
+ if ( nextResource !== null ) {
1090
+ // This is a Hoistable Resource
1091
+
1092
+ // This must come at the very end of the complete phase.
1093
+ bubbleProperties ( workInProgress ) ;
1094
+ preloadResourceAndSuspendIfNeeded (
1095
+ workInProgress ,
1096
+ nextResource ,
1097
+ type ,
1098
+ newProps ,
1099
+ renderLanes ,
1100
+ ) ;
1101
+ return null ;
1036
1102
} else {
1037
- maySuspend = maySuspendCommit (
1038
- workInProgress . type ,
1039
- workInProgress . pendingProps ,
1103
+ // This is a Hoistable Instance
1104
+
1105
+ // This must come at the very end of the complete phase.
1106
+ bubbleProperties ( workInProgress ) ;
1107
+ preloadInstanceAndSuspendIfNeeded (
1108
+ workInProgress ,
1109
+ type ,
1110
+ newProps ,
1111
+ renderLanes ,
1040
1112
) ;
1113
+ return null ;
1041
1114
}
1042
- markUpdate ( workInProgress ) ;
1043
- } else if ( workInProgress . memoizedState === null ) {
1044
- maySuspend = maySuspendCommit (
1045
- workInProgress . type ,
1046
- workInProgress . pendingProps ,
1047
- ) ;
1048
- // We may have props to update on the Hoistable instance. We use the
1049
- // updateHostComponent path becuase it produces the update queue
1050
- // we need for Hoistables
1051
- updateHostComponent (
1052
- current ,
1053
- workInProgress ,
1054
- workInProgress . type ,
1055
- workInProgress . pendingProps ,
1056
- renderLanes ,
1057
- ) ;
1058
- }
1059
- bubbleProperties ( workInProgress ) ;
1060
-
1061
- // This must come at the very end of the complete phase, because it might
1062
- // throw to suspend, and if the resource immediately loads, the work loop
1063
- // will resume rendering as if the work-in-progress completed. So it must
1064
- // fully complete.
1065
- if ( maySuspend ) {
1066
- preloadInstanceAndSuspendIfNeeded (
1067
- workInProgress ,
1068
- workInProgress . type ,
1069
- workInProgress . pendingProps ,
1070
- renderLanes ,
1071
- ) ;
1072
1115
} else {
1073
- workInProgress . flags &= ~ SuspenseyCommit ;
1116
+ // We are updating.
1117
+ const currentResource = current . memoizedState ;
1118
+ if ( nextResource !== currentResource ) {
1119
+ // We are transitioning to, from, or between Hoistable Resources
1120
+ // and require an update
1121
+ markUpdate ( workInProgress ) ;
1122
+ }
1123
+ if ( current . ref !== workInProgress . ref ) {
1124
+ markRef ( workInProgress ) ;
1125
+ }
1126
+ if ( nextResource !== null ) {
1127
+ // This is a Hoistable Resource
1128
+ // This must come at the very end of the complete phase.
1129
+
1130
+ bubbleProperties ( workInProgress ) ;
1131
+ if ( nextResource === currentResource ) {
1132
+ workInProgress . flags &= ~ SuspenseyCommit ;
1133
+ } else {
1134
+ preloadResourceAndSuspendIfNeeded (
1135
+ workInProgress ,
1136
+ nextResource ,
1137
+ type ,
1138
+ newProps ,
1139
+ renderLanes ,
1140
+ ) ;
1141
+ }
1142
+ return null ;
1143
+ } else {
1144
+ // This is a Hoistable Instance
1145
+ //
1146
+ // We may have props to update on the Hoistable instance. We use the
1147
+ // updateHostComponent path becuase it produces the update queue
1148
+ // we need for Hoistables.
1149
+ updateHostComponent (
1150
+ current ,
1151
+ workInProgress ,
1152
+ type ,
1153
+ newProps ,
1154
+ renderLanes ,
1155
+ ) ;
1156
+
1157
+ // This must come at the very end of the complete phase.
1158
+ bubbleProperties ( workInProgress ) ;
1159
+ preloadInstanceAndSuspendIfNeeded (
1160
+ workInProgress ,
1161
+ type ,
1162
+ newProps ,
1163
+ renderLanes ,
1164
+ ) ;
1165
+ return null ;
1166
+ }
1074
1167
}
1075
- return null ;
1076
1168
}
1077
1169
}
1078
1170
// eslint-disable-next-line-no-fallthrough
@@ -1141,7 +1233,6 @@ function completeWork(
1141
1233
case HostComponent : {
1142
1234
popHostContext ( workInProgress ) ;
1143
1235
const type = workInProgress . type ;
1144
- const maySuspend = maySuspendCommit ( type , newProps ) ;
1145
1236
if ( current !== null && workInProgress . stateNode != null ) {
1146
1237
updateHostComponent (
1147
1238
current ,
@@ -1222,16 +1313,12 @@ function completeWork(
1222
1313
// throw to suspend, and if the resource immediately loads, the work loop
1223
1314
// will resume rendering as if the work-in-progress completed. So it must
1224
1315
// fully complete.
1225
- if ( maySuspend ) {
1226
- preloadInstanceAndSuspendIfNeeded (
1227
- workInProgress ,
1228
- type ,
1229
- newProps ,
1230
- renderLanes ,
1231
- ) ;
1232
- } else {
1233
- workInProgress . flags &= ~ SuspenseyCommit ;
1234
- }
1316
+ preloadInstanceAndSuspendIfNeeded (
1317
+ workInProgress ,
1318
+ workInProgress . type ,
1319
+ workInProgress . pendingProps ,
1320
+ renderLanes ,
1321
+ ) ;
1235
1322
return null ;
1236
1323
}
1237
1324
case HostText : {
0 commit comments