Don't return from perform/completeUnitOfWork (#18566)
* Remove unnecessary workInProgress line * Mutate workInProgress instead of returning We were ambivalent about this before. * Make handleError a void method too
Sebastian Markbåge committed
Apr 9, 2020 at 17:12 UTC
348ed0e93f1c9402538fa1cde980b3cce4ec69c4
2 files changed
+92
-82
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+46
-41
@@ -1200,7 +1200,7 @@ function prepareFreshStack(root, expirationTime) {
1200
}
1201
}
1202
1203
-function handleError(root, thrownValue) {
1203
+function handleError(root, thrownValue): void {
1204
do {
1205
try {
1206
// Reset module-level state that was set during the render phase.
@@ -1225,7 +1225,7 @@ function handleError(root, thrownValue) {
1225
// interntionally not calling those, we need set it here.
1226
// TODO: Consider calling `unwindWork` to pop the contexts.
1227
workInProgress = null;
1228
- return null;
1228
+ return;
1229
}
1230
1231
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -1242,7 +1242,7 @@ function handleError(root, thrownValue) {
1242
thrownValue,
1243
renderExpirationTime,
1244
);
1245
- workInProgress = completeUnitOfWork(workInProgress);
1245
+ completeUnitOfWork(workInProgress);
1246
} catch (yetAnotherThrownValue) {
1247
// Something in the return path also threw.
1248
thrownValue = yetAnotherThrownValue;
@@ -1423,7 +1423,7 @@ function renderRootSync(root, expirationTime) {
1423
function workLoopSync() {
1424
// Already timed out, so perform work without checking if we need to yield.
1425
while (workInProgress !== null) {
1426
- workInProgress = performUnitOfWork(workInProgress);
1426
+ performUnitOfWork(workInProgress);
1427
}
1428
}
1429
@@ -1474,11 +1474,11 @@ function renderRootConcurrent(root, expirationTime) {
1474
function workLoopConcurrent() {
1475
// Perform work until Scheduler asks us to yield
1476
while (workInProgress !== null && !shouldYield()) {
1477
- workInProgress = performUnitOfWork(workInProgress);
1477
+ performUnitOfWork(workInProgress);
1478
}
1479
}
1480
1481
-function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
1481
+function performUnitOfWork(unitOfWork: Fiber): void {
1482
// The current, flushed, state of this fiber is the alternate. Ideally
1483
// nothing should rely on this, but relying on it here means that we don't
1484
// need an additional field on the work in progress.
@@ -1498,45 +1498,47 @@ function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
1498
unitOfWork.memoizedProps = unitOfWork.pendingProps;
1499
if (next === null) {
1500
// If this doesn't spawn new work, complete the current work.
1501
- next = completeUnitOfWork(unitOfWork);
1501
+ completeUnitOfWork(unitOfWork);
1502
+ } else {
1503
+ workInProgress = next;
1504
}
1505
1506
ReactCurrentOwner.current = null;
1505
- return next;
1507
}
1508
1508
-function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1509
+function completeUnitOfWork(unitOfWork: Fiber): void {
1510
// Attempt to complete the current unit of work, then move to the next
1511
// sibling. If there are no more siblings, return to the parent fiber.
1511
- workInProgress = unitOfWork;
1512
+ let completedWork = unitOfWork;
1513
do {
1514
// The current, flushed, state of this fiber is the alternate. Ideally
1515
// nothing should rely on this, but relying on it here means that we don't
1516
// need an additional field on the work in progress.
1516
- const current = workInProgress.alternate;
1517
- const returnFiber = workInProgress.return;
1517
+ const current = completedWork.alternate;
1518
+ const returnFiber = completedWork.return;
1519
1520
// Check if the work completed or if something threw.
1520
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1521
- setCurrentDebugFiberInDEV(workInProgress);
1521
+ if ((completedWork.effectTag & Incomplete) === NoEffect) {
1522
+ setCurrentDebugFiberInDEV(completedWork);
1523
let next;
1524
if (
1525
!enableProfilerTimer ||
1525
- (workInProgress.mode & ProfileMode) === NoMode
1526
+ (completedWork.mode & ProfileMode) === NoMode
1527
) {
1527
- next = completeWork(current, workInProgress, renderExpirationTime);
1528
+ next = completeWork(current, completedWork, renderExpirationTime);
1529
} else {
1529
- startProfilerTimer(workInProgress);
1530
- next = completeWork(current, workInProgress, renderExpirationTime);
1530
+ startProfilerTimer(completedWork);
1531
+ next = completeWork(current, completedWork, renderExpirationTime);
1532
// Update render duration assuming we didn't error.
1532
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1533
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1534
}
1535
resetCurrentDebugFiberInDEV();
1535
- resetChildExpirationTime(workInProgress);
1536
+ resetChildExpirationTime(completedWork);
1537
1538
if (next !== null) {
1539
// Completing this fiber spawned new work. Work on that next.
1539
- return next;
1540
+ workInProgress = next;
1541
+ return;
1542
}
1543
1544
if (
@@ -1548,13 +1550,13 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1550
// list of the parent. The completion order of the children affects the
1551
// side-effect order.
1552
if (returnFiber.firstEffect === null) {
1551
- returnFiber.firstEffect = workInProgress.firstEffect;
1553
+ returnFiber.firstEffect = completedWork.firstEffect;
1554
}
1553
- if (workInProgress.lastEffect !== null) {
1555
+ if (completedWork.lastEffect !== null) {
1556
if (returnFiber.lastEffect !== null) {
1555
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1557
+ returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
1558
}
1557
- returnFiber.lastEffect = workInProgress.lastEffect;
1559
+ returnFiber.lastEffect = completedWork.lastEffect;
1560
}
1561
1562
// If this fiber had side-effects, we append it AFTER the children's
@@ -1563,43 +1565,43 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1565
// schedule our own side-effect on our own list because if end up
1566
// reusing children we'll schedule this effect onto itself since we're
1567
// at the end.
1566
- const effectTag = workInProgress.effectTag;
1568
+ const effectTag = completedWork.effectTag;
1569
1570
// Skip both NoWork and PerformedWork tags when creating the effect
1571
// list. PerformedWork effect is read by React DevTools but shouldn't be
1572
// committed.
1573
if (effectTag > PerformedWork) {
1574
if (returnFiber.lastEffect !== null) {
1573
- returnFiber.lastEffect.nextEffect = workInProgress;
1575
+ returnFiber.lastEffect.nextEffect = completedWork;
1576
} else {
1575
- returnFiber.firstEffect = workInProgress;
1577
+ returnFiber.firstEffect = completedWork;
1578
}
1577
- returnFiber.lastEffect = workInProgress;
1579
+ returnFiber.lastEffect = completedWork;
1580
}
1581
}
1582
} else {
1583
// This fiber did not complete because something threw. Pop values off
1584
// the stack without entering the complete phase. If this is a boundary,
1585
// capture values if possible.
1584
- const next = unwindWork(workInProgress, renderExpirationTime);
1586
+ const next = unwindWork(completedWork, renderExpirationTime);
1587
1588
// Because this fiber did not complete, don't reset its expiration time.
1589
1590
if (
1591
enableProfilerTimer &&
1590
- (workInProgress.mode & ProfileMode) !== NoMode
1592
+ (completedWork.mode & ProfileMode) !== NoMode
1593
) {
1594
// Record the render duration for the fiber that errored.
1593
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1595
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1596
1597
// Include the time spent working on failed children before continuing.
1596
- let actualDuration = workInProgress.actualDuration;
1597
- let child = workInProgress.child;
1598
+ let actualDuration = completedWork.actualDuration;
1599
+ let child = completedWork.child;
1600
while (child !== null) {
1601
actualDuration += child.actualDuration;
1602
child = child.sibling;
1603
}
1602
- workInProgress.actualDuration = actualDuration;
1604
+ completedWork.actualDuration = actualDuration;
1605
}
1606
1607
if (next !== null) {
@@ -1608,7 +1610,8 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1610
// Since we're restarting, remove anything that is not a host effect
1611
// from the effect tag.
1612
next.effectTag &= HostEffectMask;
1611
- return next;
1613
+ workInProgress = next;
1614
+ return;
1615
}
1616
1617
if (returnFiber !== null) {
@@ -1618,20 +1621,22 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1621
}
1622
}
1623
1621
- const siblingFiber = workInProgress.sibling;
1624
+ const siblingFiber = completedWork.sibling;
1625
if (siblingFiber !== null) {
1626
// If there is more work to do in this returnFiber, do that next.
1624
- return siblingFiber;
1627
+ workInProgress = siblingFiber;
1628
+ return;
1629
}
1630
// Otherwise, return to the parent
1627
- workInProgress = returnFiber;
1628
- } while (workInProgress !== null);
1631
+ completedWork = returnFiber;
1632
+ // Update the next thing we're working on in case something throws.
1633
+ workInProgress = completedWork;
1634
+ } while (completedWork !== null);
1635
1636
// We've reached the root.
1637
if (workInProgressRootExitStatus === RootIncomplete) {
1638
workInProgressRootExitStatus = RootCompleted;
1639
}
1634
- return null;
1640
}
1641
1642
function getRemainingExpirationTime(fiber: Fiber) {
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+46
-41
@@ -1208,7 +1208,7 @@ function prepareFreshStack(root, expirationTime) {
1208
}
1209
}
1210
1211
-function handleError(root, thrownValue) {
1211
+function handleError(root, thrownValue): void {
1212
do {
1213
try {
1214
// Reset module-level state that was set during the render phase.
@@ -1233,7 +1233,7 @@ function handleError(root, thrownValue) {
1233
// interntionally not calling those, we need set it here.
1234
// TODO: Consider calling `unwindWork` to pop the contexts.
1235
workInProgress = null;
1236
- return null;
1236
+ return;
1237
}
1238
1239
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
@@ -1250,7 +1250,7 @@ function handleError(root, thrownValue) {
1250
thrownValue,
1251
renderExpirationTime,
1252
);
1253
- workInProgress = completeUnitOfWork(workInProgress);
1253
+ completeUnitOfWork(workInProgress);
1254
} catch (yetAnotherThrownValue) {
1255
// Something in the return path also threw.
1256
thrownValue = yetAnotherThrownValue;
@@ -1442,7 +1442,7 @@ function renderRootSync(root, expirationTime) {
1442
function workLoopSync() {
1443
// Already timed out, so perform work without checking if we need to yield.
1444
while (workInProgress !== null) {
1445
- workInProgress = performUnitOfWork(workInProgress);
1445
+ performUnitOfWork(workInProgress);
1446
}
1447
}
1448
@@ -1493,11 +1493,11 @@ function renderRootConcurrent(root, expirationTime) {
1493
function workLoopConcurrent() {
1494
// Perform work until Scheduler asks us to yield
1495
while (workInProgress !== null && !shouldYield()) {
1496
- workInProgress = performUnitOfWork(workInProgress);
1496
+ performUnitOfWork(workInProgress);
1497
}
1498
}
1499
1500
-function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
1500
+function performUnitOfWork(unitOfWork: Fiber): void {
1501
// The current, flushed, state of this fiber is the alternate. Ideally
1502
// nothing should rely on this, but relying on it here means that we don't
1503
// need an additional field on the work in progress.
@@ -1517,45 +1517,47 @@ function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
1517
unitOfWork.memoizedProps = unitOfWork.pendingProps;
1518
if (next === null) {
1519
// If this doesn't spawn new work, complete the current work.
1520
- next = completeUnitOfWork(unitOfWork);
1520
+ completeUnitOfWork(unitOfWork);
1521
+ } else {
1522
+ workInProgress = next;
1523
}
1524
1525
ReactCurrentOwner.current = null;
1524
- return next;
1526
}
1527
1527
-function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1528
+function completeUnitOfWork(unitOfWork: Fiber): void {
1529
// Attempt to complete the current unit of work, then move to the next
1530
// sibling. If there are no more siblings, return to the parent fiber.
1530
- workInProgress = unitOfWork;
1531
+ let completedWork = unitOfWork;
1532
do {
1533
// The current, flushed, state of this fiber is the alternate. Ideally
1534
// nothing should rely on this, but relying on it here means that we don't
1535
// need an additional field on the work in progress.
1535
- const current = workInProgress.alternate;
1536
- const returnFiber = workInProgress.return;
1536
+ const current = completedWork.alternate;
1537
+ const returnFiber = completedWork.return;
1538
1539
// Check if the work completed or if something threw.
1539
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
1540
- setCurrentDebugFiberInDEV(workInProgress);
1540
+ if ((completedWork.effectTag & Incomplete) === NoEffect) {
1541
+ setCurrentDebugFiberInDEV(completedWork);
1542
let next;
1543
if (
1544
!enableProfilerTimer ||
1544
- (workInProgress.mode & ProfileMode) === NoMode
1545
+ (completedWork.mode & ProfileMode) === NoMode
1546
) {
1546
- next = completeWork(current, workInProgress, renderExpirationTime);
1547
+ next = completeWork(current, completedWork, renderExpirationTime);
1548
} else {
1548
- startProfilerTimer(workInProgress);
1549
- next = completeWork(current, workInProgress, renderExpirationTime);
1549
+ startProfilerTimer(completedWork);
1550
+ next = completeWork(current, completedWork, renderExpirationTime);
1551
// Update render duration assuming we didn't error.
1551
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1552
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1553
}
1554
resetCurrentDebugFiberInDEV();
1554
- resetChildExpirationTime(workInProgress);
1555
+ resetChildExpirationTime(completedWork);
1556
1557
if (next !== null) {
1558
// Completing this fiber spawned new work. Work on that next.
1558
- return next;
1559
+ workInProgress = next;
1560
+ return;
1561
}
1562
1563
if (
@@ -1567,13 +1569,13 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1569
// list of the parent. The completion order of the children affects the
1570
// side-effect order.
1571
if (returnFiber.firstEffect === null) {
1570
- returnFiber.firstEffect = workInProgress.firstEffect;
1572
+ returnFiber.firstEffect = completedWork.firstEffect;
1573
}
1572
- if (workInProgress.lastEffect !== null) {
1574
+ if (completedWork.lastEffect !== null) {
1575
if (returnFiber.lastEffect !== null) {
1574
- returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
1576
+ returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
1577
}
1576
- returnFiber.lastEffect = workInProgress.lastEffect;
1578
+ returnFiber.lastEffect = completedWork.lastEffect;
1579
}
1580
1581
// If this fiber had side-effects, we append it AFTER the children's
@@ -1582,43 +1584,43 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1584
// schedule our own side-effect on our own list because if end up
1585
// reusing children we'll schedule this effect onto itself since we're
1586
// at the end.
1585
- const effectTag = workInProgress.effectTag;
1587
+ const effectTag = completedWork.effectTag;
1588
1589
// Skip both NoWork and PerformedWork tags when creating the effect
1590
// list. PerformedWork effect is read by React DevTools but shouldn't be
1591
// committed.
1592
if (effectTag > PerformedWork) {
1593
if (returnFiber.lastEffect !== null) {
1592
- returnFiber.lastEffect.nextEffect = workInProgress;
1594
+ returnFiber.lastEffect.nextEffect = completedWork;
1595
} else {
1594
- returnFiber.firstEffect = workInProgress;
1596
+ returnFiber.firstEffect = completedWork;
1597
}
1596
- returnFiber.lastEffect = workInProgress;
1598
+ returnFiber.lastEffect = completedWork;
1599
}
1600
}
1601
} else {
1602
// This fiber did not complete because something threw. Pop values off
1603
// the stack without entering the complete phase. If this is a boundary,
1604
// capture values if possible.
1603
- const next = unwindWork(workInProgress, renderExpirationTime);
1605
+ const next = unwindWork(completedWork, renderExpirationTime);
1606
1607
// Because this fiber did not complete, don't reset its expiration time.
1608
1609
if (
1610
enableProfilerTimer &&
1609
- (workInProgress.mode & ProfileMode) !== NoMode
1611
+ (completedWork.mode & ProfileMode) !== NoMode
1612
) {
1613
// Record the render duration for the fiber that errored.
1612
- stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
1614
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
1615
1616
// Include the time spent working on failed children before continuing.
1615
- let actualDuration = workInProgress.actualDuration;
1616
- let child = workInProgress.child;
1617
+ let actualDuration = completedWork.actualDuration;
1618
+ let child = completedWork.child;
1619
while (child !== null) {
1620
actualDuration += child.actualDuration;
1621
child = child.sibling;
1622
}
1621
- workInProgress.actualDuration = actualDuration;
1623
+ completedWork.actualDuration = actualDuration;
1624
}
1625
1626
if (next !== null) {
@@ -1627,7 +1629,8 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1629
// Since we're restarting, remove anything that is not a host effect
1630
// from the effect tag.
1631
next.effectTag &= HostEffectMask;
1630
- return next;
1632
+ workInProgress = next;
1633
+ return;
1634
}
1635
1636
if (returnFiber !== null) {
@@ -1637,20 +1640,22 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1640
}
1641
}
1642
1640
- const siblingFiber = workInProgress.sibling;
1643
+ const siblingFiber = completedWork.sibling;
1644
if (siblingFiber !== null) {
1645
// If there is more work to do in this returnFiber, do that next.
1643
- return siblingFiber;
1646
+ workInProgress = siblingFiber;
1647
+ return;
1648
}
1649
// Otherwise, return to the parent
1646
- workInProgress = returnFiber;
1647
- } while (workInProgress !== null);
1650
+ completedWork = returnFiber;
1651
+ // Update the next thing we're working on in case something throws.
1652
+ workInProgress = completedWork;
1653
+ } while (completedWork !== null);
1654
1655
// We've reached the root.
1656
if (workInProgressRootExitStatus === RootIncomplete) {
1657
workInProgressRootExitStatus = RootCompleted;
1658
}
1653
- return null;
1659
}
1660
1661
function getRemainingExpirationTime(fiber: Fiber) {