10
import type {Fiber, FiberRoot} from './ReactInternalTypes';
11
import type {RootState} from './ReactFiberRoot';
12
import type {Lanes, Lane} from './ReactFiberLane';
13
-import type {
14
- ReactScopeInstance,
15
- ReactContext,
16
- Wakeable,
17
-} from 'shared/ReactTypes';
13
+import type {ReactScopeInstance, ReactContext} from 'shared/ReactTypes';
14
import type {
15
Instance,
16
Type,
21
import type {
22
SuspenseState,
23
SuspenseListRenderState,
24
+ RetryQueue,
25
} from './ReactFiberSuspenseComponent';
26
+import type {OffscreenQueue} from './ReactFiberOffscreenComponent';
27
import {isOffscreenManual} from './ReactFiberOffscreenComponent';
28
import type {OffscreenState} from './ReactFiberOffscreenComponent';
29
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
88
Incomplete,
89
ShouldCapture,
90
ForceClientRender,
91
+ SuspenseyCommit,
92
+ ScheduleRetry,
93
} from './ReactFiberFlags';
94
95
import {
111
finalizeContainerChildren,
112
preparePortalMount,
113
prepareScopeUpdate,
114
+ shouldSuspendCommit,
115
} from './ReactFiberHostConfig';
116
import {
117
getRootHostContainer,
151
renderHasNotSuspendedYet,
152
getRenderTargetTime,
153
getWorkInProgressTransitions,
154
+ shouldRemainOnPreviousScreen,
155
} from './ReactFiberWorkLoop';
156
import {
157
OffscreenLane,
159
NoLanes,
160
includesSomeLane,
161
mergeLanes,
162
+ claimNextRetryLane,
163
+ includesOnlyNonUrgentLanes,
164
} from './ReactFiberLane';
165
import {resetChildFibers} from './ReactChildFiber';
166
import {createScopeInstance} from './ReactFiberScope';
172
popMarkerInstance,
173
popRootMarkerInstance,
174
} from './ReactFiberTracingMarkerComponent';
175
+import {suspendCommit} from './ReactFiberThenable';
176
177
function markUpdate(workInProgress: Fiber) {
178
// Tag the fiber with an update effect. This turns a Placement into
416
workInProgress: Fiber,
417
type: Type,
418
newProps: Props,
419
+ renderLanes: Lanes,
420
) {
421
if (supportsMutation) {
422
// If we have an alternate, that means this is an update and we need to
433
// TODO: Split the update API as separate for the props vs. children.
434
// Even better would be if children weren't special cased at all tho.
435
const instance: Instance = workInProgress.stateNode;
436
+
437
+ suspendHostCommitIfNeeded(workInProgress, type, newProps, renderLanes);
438
+
439
const currentHostContext = getHostContext();
440
// TODO: Experiencing an error where oldProps is null. Suggests a host
441
// component is hitting the resume path. Figure out why. Possibly
494
childrenUnchanged,
495
recyclableInstance,
496
);
497
+
498
+ suspendHostCommitIfNeeded(workInProgress, type, newProps, renderLanes);
499
+
500
if (
501
finalizeInitialChildren(newInstance, type, newProps, currentHostContext)
502
) {
514
}
515
}
516
}
517
+
518
+// TODO: This should ideally move to begin phase, but currently the instance is
519
+// not created until the complete phase. For our existing use cases, host nodes
520
+// that suspend don't have children, so it doesn't matter. But that might not
521
+// always be true in the future.
522
+function suspendHostCommitIfNeeded(
523
+ workInProgress: Fiber,
524
+ type: Type,
525
+ props: Props,
526
+ renderLanes: Lanes,
527
+) {
528
+ // Ask the renderer if this instance should suspend the commit.
529
+ if (!shouldSuspendCommit(type, props)) {
530
+ // If this flag was set previously, we can remove it. The flag represents
531
+ // whether this particular set of props might ever need to suspend. The
532
+ // safest thing to do is for shouldSuspendCommit to always return true, but
533
+ // if the renderer is reasonably confident that the underlying resource
534
+ // won't be evicted, it can return false as a performance optimization.
535
+ workInProgress.flags &= ~SuspenseyCommit;
536
+ return;
537
+ }
538
+
539
+ // Mark this fiber with a flag. We use this right before the commit phase to
540
+ // find all the fibers that might need to suspend the commit. In the future
541
+ // we'll also use it when revealing a hidden tree. It gets set even if we
542
+ // don't end up suspending this particular commit, because if this tree ever
543
+ // becomes hidden, we might want to suspend before revealing it again.
544
+ workInProgress.flags |= SuspenseyCommit;
545
+
546
+ // Check if we're rendering at a "non-urgent" priority. This is the same
547
+ // check that `useDeferredValue` does to determine whether it needs to
548
+ // defer. This is partly for gradual adoption purposes (i.e. shouldn't start
549
+ // suspending until you opt in with startTransition or Suspense) but it
550
+ // also happens to be the desired behavior for the concrete use cases we've
551
+ // thought of so far, like CSS loading, fonts, images, etc.
552
+ // TODO: We may decide to expose a way to force a fallback even during a
553
+ // sync update.
554
+ if (!includesOnlyNonUrgentLanes(renderLanes)) {
555
+ // This is an urgent render. Never suspend or trigger a fallback.
556
+ } else {
557
+ // Need to decide whether to activate the nearest fallback or to continue
558
+ // rendering and suspend right before the commit phase.
559
+ if (shouldRemainOnPreviousScreen()) {
560
+ // It's OK to block the commit. Don't show a fallback.
561
+ } else {
562
+ // We shouldn't block the commit. Activate a fallback at the nearest
563
+ // Suspense boundary.
564
+ suspendCommit();
565
+ }
566
+ }
567
+}
568
+
569
+function scheduleRetryEffect(
570
+ workInProgress: Fiber,
571
+ retryQueue: RetryQueue | null,
572
+) {
573
+ const wakeables = retryQueue;
574
+ if (wakeables !== null) {
575
+ // Schedule an effect to attach a retry listener to the promise.
576
+ // TODO: Move to passive phase
577
+ workInProgress.flags |= Update;
578
+ } else {
579
+ // This boundary suspended, but no wakeables were added to the retry
580
+ // queue. Check if the renderer suspended commit. If so, this means
581
+ // that once the fallback is committed, we can immediately retry
582
+ // rendering again, because rendering wasn't actually blocked. Only
583
+ // the commit phase.
584
+ // TODO: Consider a model where we always schedule an immediate retry, even
585
+ // for normal Suspense. That way the retry can partially render up to the
586
+ // first thing that suspends.
587
+ if (workInProgress.flags & ScheduleRetry) {
588
+ const retryLane =
589
+ // TODO: This check should probably be moved into claimNextRetryLane
590
+ // I also suspect that we need some further consolidation of offscreen
591
+ // and retry lanes.
592
+ workInProgress.tag !== OffscreenComponent
593
+ ? claimNextRetryLane()
594
+ : OffscreenLane;
595
+ workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
596
+ }
597
+ }
598
+}
599
+
600
function updateHostText(
601
current: Fiber,
602
workInProgress: Fiber,
1050
workInProgress,
1051
workInProgress.type,
1052
workInProgress.pendingProps,
1053
+ renderLanes,
1054
);
1055
}
1056
bubbleProperties(workInProgress);
1064
const rootContainerInstance = getRootHostContainer();
1065
const type = workInProgress.type;
1066
if (current !== null && workInProgress.stateNode != null) {
971
- updateHostComponent(current, workInProgress, type, newProps);
1067
+ updateHostComponent(
1068
+ current,
1069
+ workInProgress,
1070
+ type,
1071
+ newProps,
1072
+ renderLanes,
1073
+ );
1074
1075
if (current.ref !== workInProgress.ref) {
1076
markRef(workInProgress);
1091
1092
const currentHostContext = getHostContext();
1093
const wasHydrated = popHydrationState(workInProgress);
1094
+ let instance: Instance;
1095
if (wasHydrated) {
1096
// We ignore the boolean indicating there is an updateQueue because
1097
// it is used only to set text children and HostSingletons do not
1098
// use them.
1099
prepareToHydrateHostInstance(workInProgress, currentHostContext);
1100
+ instance = workInProgress.stateNode;
1101
} else {
998
- workInProgress.stateNode = resolveSingletonInstance(
1102
+ instance = resolveSingletonInstance(
1103
type,
1104
newProps,
1105
rootContainerInstance,
1106
currentHostContext,
1107
true,
1108
);
1109
+ workInProgress.stateNode = instance;
1110
markUpdate(workInProgress);
1111
}
1112
1124
popHostContext(workInProgress);
1125
const type = workInProgress.type;
1126
if (current !== null && workInProgress.stateNode != null) {
1022
- updateHostComponent(current, workInProgress, type, newProps);
1127
+ updateHostComponent(
1128
+ current,
1129
+ workInProgress,
1130
+ type,
1131
+ newProps,
1132
+ renderLanes,
1133
+ );
1134
1135
if (current.ref !== workInProgress.ref) {
1136
markRef(workInProgress);
1192
}
1193
}
1194
1195
+ suspendHostCommitIfNeeded(workInProgress, type, newProps, renderLanes);
1196
+
1197
if (workInProgress.ref !== null) {
1198
// If there is a ref on a host node we need to schedule a callback
1199
markRef(workInProgress);
1340
}
1341
}
1342
1230
- const wakeables: Set<Wakeable> | null = (workInProgress.updateQueue: any);
1231
- if (wakeables !== null) {
1232
- // Schedule an effect to attach a retry listener to the promise.
1233
- // TODO: Move to passive phase
1234
- workInProgress.flags |= Update;
1235
- }
1343
+ const retryQueue: RetryQueue | null = (workInProgress.updateQueue: any);
1344
+ scheduleRetryEffect(workInProgress, retryQueue);
1345
1346
if (
1347
enableSuspenseCallback &&
1446
// We might bail out of the loop before finding any but that
1447
// doesn't matter since that means that the other boundaries that
1448
// we did find already has their listeners attached.
1340
- const newThenables = suspended.updateQueue;
1341
- if (newThenables !== null) {
1342
- workInProgress.updateQueue = newThenables;
1343
- workInProgress.flags |= Update;
1344
- }
1449
+ const retryQueue: RetryQueue | null =
1450
+ (suspended.updateQueue: any);
1451
+ workInProgress.updateQueue = retryQueue;
1452
+ scheduleRetryEffect(workInProgress, retryQueue);
1453
1454
// Rerender the whole list, but this time, we'll force fallbacks
1455
// to stay in place.
1507
1508
// Ensure we transfer the update queue to the parent so that it doesn't
1509
// get lost if this row ends up dropped during a second pass.
1402
- const newThenables = suspended.updateQueue;
1403
- if (newThenables !== null) {
1404
- workInProgress.updateQueue = newThenables;
1405
- workInProgress.flags |= Update;
1406
- }
1510
+ const retryQueue: RetryQueue | null = (suspended.updateQueue: any);
1511
+ workInProgress.updateQueue = retryQueue;
1512
+ scheduleRetryEffect(workInProgress, retryQueue);
1513
1514
cutOffTailIfNeeded(renderState, true);
1515
// This might have been modified.
1672
}
1673
}
1674
1569
- if (workInProgress.updateQueue !== null) {
1570
- // Schedule an effect to attach Suspense retry listeners
1571
- // TODO: Move to passive phase
1572
- workInProgress.flags |= Update;
1675
+ const offscreenQueue: OffscreenQueue | null =
1676
+ (workInProgress.updateQueue: any);
1677
+ if (offscreenQueue !== null) {
1678
+ const retryQueue = offscreenQueue.retryQueue;
1679
+ scheduleRetryEffect(workInProgress, retryQueue);
1680
}
1681
1682
if (enableCache) {