17
Props,
18
Container,
19
ChildSet,
20
+ Resource,
21
} from './ReactFiberHostConfig';
22
import type {
23
SuspenseState,
87
MutationMask,
88
Passive,
89
ForceClientRender,
89
- SuspenseyCommit,
90
+ MaySuspendCommit,
91
ScheduleRetry,
92
+ ShouldSuspendCommit,
93
} from './ReactFiberFlags';
94
95
import {
114
maySuspendCommit,
115
mayResourceSuspendCommit,
116
preloadInstance,
117
+ preloadResource,
118
} from './ReactFiberHostConfig';
119
import {
120
getRootHostContainer,
155
getRenderTargetTime,
156
getWorkInProgressTransitions,
157
shouldRemainOnPreviousScreen,
158
+ getWorkInProgressRootRenderLanes,
159
} from './ReactFiberWorkLoop';
160
import {
161
OffscreenLane,
515
}
516
}
517
518
+// This function must be called at the very end of the complete phase, because
519
+// it might throw to suspend, and if the resource immediately loads, the work
520
+// loop will resume rendering as if the work-in-progress completed. So it must
521
+// fully complete.
522
// TODO: This should ideally move to begin phase, but currently the instance is
523
// not created until the complete phase. For our existing use cases, host nodes
524
// that suspend don't have children, so it doesn't matter. But that might not
529
props: Props,
530
renderLanes: Lanes,
531
) {
524
- workInProgress.flags |= SuspenseyCommit;
532
+ if (!maySuspendCommit(type, props)) {
533
+ // If this flag was set previously, we can remove it. The flag
534
+ // represents whether this particular set of props might ever need to
535
+ // suspend. The safest thing to do is for maySuspendCommit to always
536
+ // return true, but if the renderer is reasonably confident that the
537
+ // underlying resource won't be evicted, it can return false as a
538
+ // performance optimization.
539
+ workInProgress.flags &= ~MaySuspendCommit;
540
+ return;
541
+ }
542
+
543
+ // Mark this fiber with a flag. This gets set on all host instances
544
+ // that might possibly suspend, even if they don't need to suspend
545
+ // currently. We use this when revealing a prerendered tree, because
546
+ // even though the tree has "mounted", its resources might not have
547
+ // loaded yet.
548
+ workInProgress.flags |= MaySuspendCommit;
549
+
550
// Check if we're rendering at a "non-urgent" priority. This is the same
551
// check that `useDeferredValue` does to determine whether it needs to
552
// defer. This is partly for gradual adoption purposes (i.e. shouldn't start
553
// suspending until you opt in with startTransition or Suspense) but it
554
// also happens to be the desired behavior for the concrete use cases we've
555
// thought of so far, like CSS loading, fonts, images, etc.
556
+ //
557
+ // We check the "root" render lanes here rather than the "subtree" render
558
+ // because during a retry or offscreen prerender, the "subtree" render
559
+ // lanes may include additional "base" lanes that were deferred during
560
+ // a previous render.
561
// TODO: We may decide to expose a way to force a fallback even during a
562
// sync update.
533
- if (!includesOnlyNonUrgentLanes(renderLanes)) {
563
+ const rootRenderLanes = getWorkInProgressRootRenderLanes();
564
+ if (!includesOnlyNonUrgentLanes(rootRenderLanes)) {
565
// This is an urgent render. Don't suspend or show a fallback. Also,
566
// there's no need to preload, because we're going to commit this
567
// synchronously anyway.
575
const isReady = preloadInstance(type, props);
576
if (!isReady) {
577
if (shouldRemainOnPreviousScreen()) {
547
- // It's OK to suspend. Continue rendering.
578
+ // It's OK to suspend. Mark the fiber so we know to suspend before the
579
+ // commit phase. Then continue rendering.
580
+ workInProgress.flags |= ShouldSuspendCommit;
581
} else {
582
// Trigger a fallback rather than block the render.
583
suspendCommit();
586
}
587
}
588
589
+function preloadResourceAndSuspendIfNeeded(
590
+ workInProgress: Fiber,
591
+ resource: Resource,
592
+ type: Type,
593
+ props: Props,
594
+ renderLanes: Lanes,
595
+) {
596
+ // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
597
+ if (!mayResourceSuspendCommit(resource)) {
598
+ workInProgress.flags &= ~MaySuspendCommit;
599
+ return;
600
+ }
601
+
602
+ workInProgress.flags |= MaySuspendCommit;
603
+
604
+ const rootRenderLanes = getWorkInProgressRootRenderLanes();
605
+ if (!includesOnlyNonUrgentLanes(rootRenderLanes)) {
606
+ // This is an urgent render. Don't suspend or show a fallback.
607
+ } else {
608
+ const isReady = preloadResource(resource);
609
+ if (!isReady) {
610
+ if (shouldRemainOnPreviousScreen()) {
611
+ workInProgress.flags |= ShouldSuspendCommit;
612
+ } else {
613
+ suspendCommit();
614
+ }
615
+ }
616
+ }
617
+}
618
+
619
function scheduleRetryEffect(
620
workInProgress: Fiber,
621
retryQueue: RetryQueue | null,
1078
}
1079
case HostHoistable: {
1080
if (enableFloat && supportsResources) {
1018
- const currentRef = current ? current.ref : null;
1019
- if (currentRef !== workInProgress.ref) {
1020
- markRef(workInProgress);
1021
- }
1022
-
1023
- let maySuspend = false;
1081
+ // The branching here is more complicated than you might expect because
1082
+ // a HostHoistable sometimes corresponds to a Resource and sometimes
1083
+ // corresponds to an Instance. It can also switch during an update.
1084
1025
- // @TODO refactor this block to create the instance here in complete phase if we
1026
- // are not hydrating.
1027
- if (
1085
+ const type = workInProgress.type;
1086
+ const nextResource: Resource | null = workInProgress.memoizedState;
1087
+ if (current === null) {
1088
// 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);
1089
+ // @TODO refactor this block to create the instance here in complete
1090
+ // phase if we are not hydrating.
1091
+ markUpdate(workInProgress);
1092
+ if (workInProgress.ref !== null) {
1093
+ markRef(workInProgress);
1094
+ }
1095
+ if (nextResource !== null) {
1096
+ // This is a Hoistable Resource
1097
+
1098
+ // This must come at the very end of the complete phase.
1099
+ bubbleProperties(workInProgress);
1100
+ preloadResourceAndSuspendIfNeeded(
1101
+ workInProgress,
1102
+ nextResource,
1103
+ type,
1104
+ newProps,
1105
+ renderLanes,
1106
+ );
1107
+ return null;
1108
} else {
1037
- maySuspend = maySuspendCommit(
1038
- workInProgress.type,
1039
- workInProgress.pendingProps,
1109
+ // This is a Hoistable Instance
1110
+
1111
+ // This must come at the very end of the complete phase.
1112
+ bubbleProperties(workInProgress);
1113
+ preloadInstanceAndSuspendIfNeeded(
1114
+ workInProgress,
1115
+ type,
1116
+ newProps,
1117
+ renderLanes,
1118
);
1119
+ return null;
1120
}
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
- );
1121
} else {
1073
- workInProgress.flags &= ~SuspenseyCommit;
1122
+ // We are updating.
1123
+ const currentResource = current.memoizedState;
1124
+ if (nextResource !== currentResource) {
1125
+ // We are transitioning to, from, or between Hoistable Resources
1126
+ // and require an update
1127
+ markUpdate(workInProgress);
1128
+ }
1129
+ if (current.ref !== workInProgress.ref) {
1130
+ markRef(workInProgress);
1131
+ }
1132
+ if (nextResource !== null) {
1133
+ // This is a Hoistable Resource
1134
+ // This must come at the very end of the complete phase.
1135
+
1136
+ bubbleProperties(workInProgress);
1137
+ if (nextResource === currentResource) {
1138
+ workInProgress.flags &= ~MaySuspendCommit;
1139
+ } else {
1140
+ preloadResourceAndSuspendIfNeeded(
1141
+ workInProgress,
1142
+ nextResource,
1143
+ type,
1144
+ newProps,
1145
+ renderLanes,
1146
+ );
1147
+ }
1148
+ return null;
1149
+ } else {
1150
+ // This is a Hoistable Instance
1151
+ //
1152
+ // We may have props to update on the Hoistable instance. We use the
1153
+ // updateHostComponent path becuase it produces the update queue
1154
+ // we need for Hoistables.
1155
+ updateHostComponent(
1156
+ current,
1157
+ workInProgress,
1158
+ type,
1159
+ newProps,
1160
+ renderLanes,
1161
+ );
1162
+
1163
+ // This must come at the very end of the complete phase.
1164
+ bubbleProperties(workInProgress);
1165
+ preloadInstanceAndSuspendIfNeeded(
1166
+ workInProgress,
1167
+ type,
1168
+ newProps,
1169
+ renderLanes,
1170
+ );
1171
+ return null;
1172
+ }
1173
}
1075
- return null;
1174
}
1175
}
1176
// eslint-disable-next-line-no-fallthrough
1239
case HostComponent: {
1240
popHostContext(workInProgress);
1241
const type = workInProgress.type;
1144
- const maySuspend = maySuspendCommit(type, newProps);
1242
if (current !== null && workInProgress.stateNode != null) {
1243
updateHostComponent(
1244
current,
1319
// throw to suspend, and if the resource immediately loads, the work loop
1320
// will resume rendering as if the work-in-progress completed. So it must
1321
// fully complete.
1225
- if (maySuspend) {
1226
- preloadInstanceAndSuspendIfNeeded(
1227
- workInProgress,
1228
- type,
1229
- newProps,
1230
- renderLanes,
1231
- );
1232
- } else {
1233
- workInProgress.flags &= ~SuspenseyCommit;
1234
- }
1322
+ preloadInstanceAndSuspendIfNeeded(
1323
+ workInProgress,
1324
+ workInProgress.type,
1325
+ workInProgress.pendingProps,
1326
+ renderLanes,
1327
+ );
1328
return null;
1329
}
1330
case HostText: {