704
return next;
705
}
706
707
- function workLoop(expirationTime: ExpirationTime) {
707
+ function workLoop(isAsync) {
708
if (capturedErrors !== null) {
709
// If there are unhandled errors, switch to the slow work loop.
710
// TODO: How to avoid this check in the fast path? Maybe the renderer
711
// could keep track of which roots have unhandled errors and call a
712
// forked version of renderRoot.
713
- slowWorkLoopThatChecksForFailedWork(expirationTime);
713
+ slowWorkLoopThatChecksForFailedWork(isAsync);
714
return;
715
}
716
- if (
717
- nextRenderExpirationTime === NoWork ||
718
- nextRenderExpirationTime > expirationTime
719
- ) {
720
- return;
721
- }
722
-
723
- if (nextRenderExpirationTime <= mostRecentCurrentTime) {
716
+ if (!isAsync) {
717
// Flush all expired work.
718
while (nextUnitOfWork !== null) {
719
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
726
}
727
}
728
736
- function slowWorkLoopThatChecksForFailedWork(expirationTime: ExpirationTime) {
737
- if (
738
- nextRenderExpirationTime === NoWork ||
739
- nextRenderExpirationTime > expirationTime
740
- ) {
741
- return;
742
- }
743
-
744
- if (nextRenderExpirationTime <= mostRecentCurrentTime) {
729
+ function slowWorkLoopThatChecksForFailedWork(isAsync) {
730
+ if (!isAsync) {
731
// Flush all expired work.
732
while (nextUnitOfWork !== null) {
733
if (hasCapturedError(nextUnitOfWork)) {
754
root: FiberRoot,
755
failedWork: Fiber,
756
boundary: Fiber,
771
- expirationTime: ExpirationTime,
757
+ isAsync: boolean,
758
) {
759
// We're going to restart the error boundary that captured the error.
760
// Conceptually, we're unwinding the stack. We need to unwind the
769
nextUnitOfWork = performFailedUnitOfWork(boundary);
770
771
// Continue working.
786
- workLoop(expirationTime);
772
+ workLoop(isAsync);
773
}
774
775
function renderRoot(
776
root: FiberRoot,
777
expirationTime: ExpirationTime,
778
+ isAsync: boolean,
779
): Fiber | null {
780
invariant(
781
!isWorking,
811
let didError = false;
812
let error = null;
813
if (__DEV__) {
827
- invokeGuardedCallback(null, workLoop, null, expirationTime);
814
+ invokeGuardedCallback(null, workLoop, null, isAsync);
815
if (hasCaughtError()) {
816
didError = true;
817
error = clearCaughtError();
818
}
819
} else {
820
try {
834
- workLoop(expirationTime);
821
+ workLoop(isAsync);
822
} catch (e) {
823
didError = true;
824
error = e;
866
root,
867
failedWork,
868
boundary,
882
- expirationTime,
869
+ isAsync,
870
);
871
if (hasCaughtError()) {
872
didError = true;
875
}
876
} else {
877
try {
891
- renderRootCatchBlock(root, failedWork, boundary, expirationTime);
878
+ renderRootCatchBlock(root, failedWork, boundary, isAsync);
879
error = null;
880
} catch (e) {
881
didError = true;
1156
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1157
}
1158
1159
+ function computeInteractiveExpiration() {
1160
+ // Should complete within ~500ms. 600ms max.
1161
+ const currentTime = recalculateCurrentTime();
1162
+ const expirationMs = 500;
1163
+ const bucketSizeMs = 100;
1164
+ return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
1165
+ }
1166
+
1167
// Creates a unique async expiration time.
1168
function computeUniqueAsyncExpiration(): ExpirationTime {
1169
let result = computeAsyncExpiration();
1196
// No explicit expiration context was set, and we're not currently
1197
// performing work. Calculate a new expiration time.
1198
if (fiber.mode & AsyncMode) {
1204
- // This is an async update
1205
- expirationTime = computeAsyncExpiration();
1199
+ if (isBatchingInteractiveUpdates) {
1200
+ // This is an interactive update
1201
+ expirationTime = computeInteractiveExpiration();
1202
+ } else {
1203
+ // This is an async update
1204
+ expirationTime = computeAsyncExpiration();
1205
+ }
1206
} else {
1207
// This is a sync update
1208
expirationTime = Sync;
1209
}
1210
}
1211
+ if (isBatchingInteractiveUpdates) {
1212
+ // This is an interactive update. Keep track of the lowest pending
1213
+ // interactive expiration time. This allows us to synchronously flush
1214
+ // all interactive updates when needed.
1215
+ if (
1216
+ lowestPendingInteractiveExpirationTime === NoWork ||
1217
+ expirationTime > lowestPendingInteractiveExpirationTime
1218
+ ) {
1219
+ lowestPendingInteractiveExpirationTime = expirationTime;
1220
+ }
1221
+ }
1222
return expirationTime;
1223
}
1224
1320
}
1321
}
1322
1312
- function syncUpdates<A>(fn: () => A): A {
1323
+ function syncUpdates<A, B, C0, D, R>(
1324
+ fn: (A, B, C0, D) => R,
1325
+ a: A,
1326
+ b: B,
1327
+ c: C0,
1328
+ d: D,
1329
+ ): R {
1330
const previousExpirationContext = expirationContext;
1331
expirationContext = Sync;
1332
try {
1316
- return fn();
1333
+ return fn(a, b, c, d);
1334
} finally {
1335
expirationContext = previousExpirationContext;
1336
}
1348
let isRendering: boolean = false;
1349
let nextFlushedRoot: FiberRoot | null = null;
1350
let nextFlushedExpirationTime: ExpirationTime = NoWork;
1351
+ let lowestPendingInteractiveExpirationTime: ExpirationTime = NoWork;
1352
let deadlineDidExpire: boolean = false;
1353
let hasUnhandledError: boolean = false;
1354
let unhandledError: mixed | null = null;
1356
1357
let isBatchingUpdates: boolean = false;
1358
let isUnbatchingUpdates: boolean = false;
1359
+ let isBatchingInteractiveUpdates: boolean = false;
1360
1361
let completedBatches: Array<Batch> | null = null;
1362
1436
}
1437
1438
if (isBatchingUpdates) {
1420
- // Flush work at the end of the batch.
1439
if (isUnbatchingUpdates) {
1440
+ // Flush work at the end of the batch.
1441
// ...unless we're inside unbatchedUpdates, in which case we should
1442
// flush it now.
1443
nextFlushedRoot = root;
1444
nextFlushedExpirationTime = Sync;
1426
- performWorkOnRoot(root, Sync, recalculateCurrentTime());
1445
+ performWorkOnRoot(root, Sync, false);
1446
}
1447
return;
1448
}
1449
1450
// TODO: Get rid of Sync and use current time?
1451
if (expirationTime === Sync) {
1433
- performWork(Sync, null);
1452
+ performSyncWork();
1453
} else {
1454
scheduleCallbackWithExpiration(expirationTime);
1455
}
1532
}
1533
1534
function performAsyncWork(dl) {
1516
- performWork(NoWork, dl);
1535
+ performWork(NoWork, true, dl);
1536
+ }
1537
+
1538
+ function performSyncWork() {
1539
+ performWork(Sync, false, null);
1540
}
1541
1519
- function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
1542
+ function performWork(
1543
+ minExpirationTime: ExpirationTime,
1544
+ isAsync: boolean,
1545
+ dl: Deadline | null,
1546
+ ) {
1547
deadline = dl;
1548
1549
// Keep working on roots until there's no more work, or until the we reach
1555
stopRequestCallbackTimer(didExpire);
1556
}
1557
1531
- while (
1532
- nextFlushedRoot !== null &&
1533
- nextFlushedExpirationTime !== NoWork &&
1534
- (minExpirationTime === NoWork ||
1535
- nextFlushedExpirationTime <= minExpirationTime) &&
1536
- !deadlineDidExpire
1537
- ) {
1538
- performWorkOnRoot(
1539
- nextFlushedRoot,
1540
- nextFlushedExpirationTime,
1541
- recalculateCurrentTime(),
1542
- );
1543
- // Find the next highest priority work.
1544
- findHighestPriorityRoot();
1558
+ if (isAsync) {
1559
+ while (
1560
+ nextFlushedRoot !== null &&
1561
+ nextFlushedExpirationTime !== NoWork &&
1562
+ (minExpirationTime === NoWork ||
1563
+ minExpirationTime >= nextFlushedExpirationTime) &&
1564
+ (!deadlineDidExpire ||
1565
+ recalculateCurrentTime() >= nextFlushedExpirationTime)
1566
+ ) {
1567
+ performWorkOnRoot(
1568
+ nextFlushedRoot,
1569
+ nextFlushedExpirationTime,
1570
+ !deadlineDidExpire,
1571
+ );
1572
+ findHighestPriorityRoot();
1573
+ }
1574
+ } else {
1575
+ while (
1576
+ nextFlushedRoot !== null &&
1577
+ nextFlushedExpirationTime !== NoWork &&
1578
+ (minExpirationTime === NoWork ||
1579
+ minExpirationTime >= nextFlushedExpirationTime)
1580
+ ) {
1581
+ performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
1582
+ findHighestPriorityRoot();
1583
+ }
1584
}
1585
1586
// We're done flushing work. Either we ran out of time in this callback,
1613
// Perform work on root as if the given expiration time is the current time.
1614
// This has the effect of synchronously flushing all work up to and
1615
// including the given time.
1577
- performWorkOnRoot(root, expirationTime, expirationTime);
1616
+ performWorkOnRoot(root, expirationTime, false);
1617
finishRendering();
1618
}
1619
1645
function performWorkOnRoot(
1646
root: FiberRoot,
1647
expirationTime: ExpirationTime,
1609
- currentTime: ExpirationTime,
1648
+ isAsync: boolean,
1649
) {
1650
invariant(
1651
!isRendering,
1656
isRendering = true;
1657
1658
// Check if this is async work or sync/expired work.
1620
- if (expirationTime <= currentTime) {
1659
+ if (!isAsync) {
1660
// Flush sync work.
1661
let finishedWork = root.finishedWork;
1662
if (finishedWork !== null) {
1664
completeRoot(root, finishedWork, expirationTime);
1665
} else {
1666
root.finishedWork = null;
1628
- finishedWork = renderRoot(root, expirationTime);
1667
+ finishedWork = renderRoot(root, expirationTime, false);
1668
if (finishedWork !== null) {
1669
// We've completed the root. Commit it.
1670
completeRoot(root, finishedWork, expirationTime);
1678
completeRoot(root, finishedWork, expirationTime);
1679
} else {
1680
root.finishedWork = null;
1642
- finishedWork = renderRoot(root, expirationTime);
1681
+ finishedWork = renderRoot(root, expirationTime, true);
1682
if (finishedWork !== null) {
1683
// We've completed the root. Check the deadline one more time
1684
// before committing.
1766
} finally {
1767
isBatchingUpdates = previousIsBatchingUpdates;
1768
if (!isBatchingUpdates && !isRendering) {
1730
- performWork(Sync, null);
1769
+ performSyncWork();
1770
}
1771
}
1772
}
1773
1774
// TODO: Batching should be implemented at the renderer level, not inside
1775
// the reconciler.
1737
- function unbatchedUpdates<A>(fn: () => A): A {
1776
+ function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1777
if (isBatchingUpdates && !isUnbatchingUpdates) {
1778
isUnbatchingUpdates = true;
1779
try {
1741
- return fn();
1780
+ return fn(a);
1781
} finally {
1782
isUnbatchingUpdates = false;
1783
}
1784
}
1746
- return fn();
1785
+ return fn(a);
1786
}
1787
1788
// TODO: Batching should be implemented at the renderer level, not within
1789
// the reconciler.
1751
- function flushSync<A>(fn: () => A): A {
1790
+ function flushSync<A, R>(fn: (a: A) => R, a: A): R {
1791
+ invariant(
1792
+ !isRendering,
1793
+ 'flushSync was called from inside a lifecycle method. It cannot be ' +
1794
+ 'called when React is already rendering.',
1795
+ );
1796
const previousIsBatchingUpdates = isBatchingUpdates;
1797
isBatchingUpdates = true;
1798
try {
1755
- return syncUpdates(fn);
1799
+ return syncUpdates(fn, a);
1800
} finally {
1801
isBatchingUpdates = previousIsBatchingUpdates;
1758
- invariant(
1759
- !isRendering,
1760
- 'flushSync was called from inside a lifecycle method. It cannot be ' +
1761
- 'called when React is already rendering.',
1762
- );
1763
- performWork(Sync, null);
1802
+ performSyncWork();
1803
+ }
1804
+ }
1805
+
1806
+ function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
1807
+ if (isBatchingInteractiveUpdates) {
1808
+ return fn(a, b);
1809
+ }
1810
+
1811
+ const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
1812
+ isBatchingInteractiveUpdates = true;
1813
+ try {
1814
+ return fn(a, b);
1815
+ } finally {
1816
+ isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
1817
+ }
1818
+ }
1819
+
1820
+ function flushInteractiveUpdates() {
1821
+ if (!isRendering && lowestPendingInteractiveExpirationTime !== NoWork) {
1822
+ // Synchronously flush pending interactive updates.
1823
+ performWork(lowestPendingInteractiveExpirationTime, false, null);
1824
+ lowestPendingInteractiveExpirationTime = NoWork;
1825
}
1826
}
1827
1833
} finally {
1834
isBatchingUpdates = previousIsBatchingUpdates;
1835
if (!isBatchingUpdates && !isRendering) {
1775
- performWork(Sync, null);
1836
+ performWork(Sync, false, null);
1837
}
1838
}
1839
}
1848
flushSync,
1849
flushControlled,
1850
deferredUpdates,
1851
+ syncUpdates,
1852
+ interactiveUpdates,
1853
+ flushInteractiveUpdates,
1854
computeUniqueAsyncExpiration,
1855
};
1856
}