Skip to content

Commit 9be531c

Browse files
authored
[Fiber] Treat unwrapping React.lazy more like a use() (#34031)
While we want to get rid of React.lazy's special wrapper type and just use a Promise for the type, we still have the wrapper. However, this is still conceptually the same as a Usable in that it should be have the same if you `use(promise)` or render a Promise as a child or type position. This PR makes it behave like a `use()` when we unwrap them. We could move to a model where it actually reaches the internal of the Lazy's Promise when it unwraps but for now I leave the lazy API signature intact by just catching the Promise and then "use()" that. This lets us align on the semantics with `use()` such as the suspense yield optimization. It also lets us warn or fork based on legacy throw-a-Promise behavior where as `React.lazy` is not deprecated.
1 parent b1cbb48 commit 9be531c

File tree

4 files changed

+37
-77
lines changed

4 files changed

+37
-77
lines changed

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ import {
6868
SuspenseActionException,
6969
createThenableState,
7070
trackUsedThenable,
71+
resolveLazy,
7172
} from './ReactFiberThenable';
7273
import {readContextDuringReconciliation} from './ReactFiberNewContext';
73-
import {callLazyInitInDEV} from './ReactFiberCallUserSpace';
7474

7575
import {runWithFiberInDEV} from './ReactCurrentFiber';
7676

@@ -364,15 +364,6 @@ function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) {
364364
}
365365
}
366366

367-
function resolveLazy(lazyType: any) {
368-
if (__DEV__) {
369-
return callLazyInitInDEV(lazyType);
370-
}
371-
const payload = lazyType._payload;
372-
const init = lazyType._init;
373-
return init(payload);
374-
}
375-
376367
type ChildReconciler = (
377368
returnFiber: Fiber,
378369
currentFirstChild: Fiber | null,
@@ -698,14 +689,7 @@ function createChildReconciler(
698689
}
699690
case REACT_LAZY_TYPE: {
700691
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
701-
let resolvedChild;
702-
if (__DEV__) {
703-
resolvedChild = callLazyInitInDEV(newChild);
704-
} else {
705-
const payload = newChild._payload;
706-
const init = newChild._init;
707-
resolvedChild = init(payload);
708-
}
692+
const resolvedChild = resolveLazy((newChild: any));
709693
const created = createChild(returnFiber, resolvedChild, lanes);
710694
currentDebugInfo = prevDebugInfo;
711695
return created;
@@ -830,14 +814,7 @@ function createChildReconciler(
830814
}
831815
case REACT_LAZY_TYPE: {
832816
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
833-
let resolvedChild;
834-
if (__DEV__) {
835-
resolvedChild = callLazyInitInDEV(newChild);
836-
} else {
837-
const payload = newChild._payload;
838-
const init = newChild._init;
839-
resolvedChild = init(payload);
840-
}
817+
const resolvedChild = resolveLazy((newChild: any));
841818
const updated = updateSlot(
842819
returnFiber,
843820
oldFiber,
@@ -962,14 +939,7 @@ function createChildReconciler(
962939
}
963940
case REACT_LAZY_TYPE: {
964941
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
965-
let resolvedChild;
966-
if (__DEV__) {
967-
resolvedChild = callLazyInitInDEV(newChild);
968-
} else {
969-
const payload = newChild._payload;
970-
const init = newChild._init;
971-
resolvedChild = init(payload);
972-
}
942+
const resolvedChild = resolveLazy((newChild: any));
973943
const updated = updateFromMap(
974944
existingChildren,
975945
returnFiber,
@@ -1086,14 +1056,7 @@ function createChildReconciler(
10861056
});
10871057
break;
10881058
case REACT_LAZY_TYPE: {
1089-
let resolvedChild;
1090-
if (__DEV__) {
1091-
resolvedChild = callLazyInitInDEV((child: any));
1092-
} else {
1093-
const payload = child._payload;
1094-
const init = (child._init: any);
1095-
resolvedChild = init(payload);
1096-
}
1059+
const resolvedChild = resolveLazy((child: any));
10971060
warnOnInvalidKey(
10981061
returnFiber,
10991062
workInProgress,
@@ -1809,14 +1772,7 @@ function createChildReconciler(
18091772
);
18101773
case REACT_LAZY_TYPE: {
18111774
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
1812-
let result;
1813-
if (__DEV__) {
1814-
result = callLazyInitInDEV(newChild);
1815-
} else {
1816-
const payload = newChild._payload;
1817-
const init = newChild._init;
1818-
result = init(payload);
1819-
}
1775+
const result = resolveLazy((newChild: any));
18201776
const firstChild = reconcileChildFibersImpl(
18211777
returnFiber,
18221778
currentFirstChild,

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,8 @@ import {
302302
pushRootMarkerInstance,
303303
TransitionTracingMarker,
304304
} from './ReactFiberTracingMarkerComponent';
305-
import {
306-
callLazyInitInDEV,
307-
callComponentInDEV,
308-
callRenderInDEV,
309-
} from './ReactFiberCallUserSpace';
305+
import {callComponentInDEV, callRenderInDEV} from './ReactFiberCallUserSpace';
306+
import {resolveLazy} from './ReactFiberThenable';
310307

311308
// A special exception that's used to unwind the stack when an update flows
312309
// into a dehydrated boundary.
@@ -2020,14 +2017,7 @@ function mountLazyComponent(
20202017

20212018
const props = workInProgress.pendingProps;
20222019
const lazyComponent: LazyComponentType<any, any> = elementType;
2023-
let Component;
2024-
if (__DEV__) {
2025-
Component = callLazyInitInDEV(lazyComponent);
2026-
} else {
2027-
const payload = lazyComponent._payload;
2028-
const init = lazyComponent._init;
2029-
Component = init(payload);
2030-
}
2020+
let Component = resolveLazy(lazyComponent);
20312021
// Store the unwrapped component in the type.
20322022
workInProgress.type = Component;
20332023

packages/react-reconciler/src/ReactFiberThenable.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import type {
1414
RejectedThenable,
1515
} from 'shared/ReactTypes';
1616

17+
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
18+
19+
import {callLazyInitInDEV} from './ReactFiberCallUserSpace';
20+
1721
import {getWorkInProgressRoot} from './ReactFiberWorkLoop';
1822

1923
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -260,6 +264,27 @@ export function suspendCommit(): void {
260264
throw SuspenseyCommitException;
261265
}
262266

267+
export function resolveLazy<T>(lazyType: LazyComponentType<T, any>): T {
268+
try {
269+
if (__DEV__) {
270+
return callLazyInitInDEV(lazyType);
271+
}
272+
const payload = lazyType._payload;
273+
const init = lazyType._init;
274+
return init(payload);
275+
} catch (x) {
276+
if (x !== null && typeof x === 'object' && typeof x.then === 'function') {
277+
// This lazy Suspended. Treat this as if we called use() to unwrap it.
278+
suspendedThenable = x;
279+
if (__DEV__) {
280+
needsToResetSuspendedThenableDEV = true;
281+
}
282+
throw SuspenseException;
283+
}
284+
throw x;
285+
}
286+
}
287+
263288
// This is used to track the actual thenable that suspended so it can be
264289
// passed to the rest of the Suspense implementation — which, for historical
265290
// reasons, expects to receive a thenable.

packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,7 @@ describe('ReactLazy', () => {
198198

199199
await resolveFakeImport(Foo);
200200

201-
await waitForAll([
202-
'Foo',
203-
...(gate('alwaysThrottleRetries') ? [] : ['Foo']),
204-
]);
201+
await waitForAll(['Foo']);
205202
expect(root).not.toMatchRenderedOutput('FooBar');
206203

207204
await act(() => resolveFakeImport(Bar));
@@ -1329,11 +1326,7 @@ describe('ReactLazy', () => {
13291326
expect(ref.current).toBe(null);
13301327

13311328
await act(() => resolveFakeImport(Foo));
1332-
assertLog([
1333-
'Foo',
1334-
// pre-warming
1335-
'Foo',
1336-
]);
1329+
assertLog(['Foo']);
13371330

13381331
await act(() => resolveFakeImport(ForwardRefBar));
13391332
assertLog(['Foo', 'forwardRef', 'Bar']);
@@ -1493,11 +1486,7 @@ describe('ReactLazy', () => {
14931486
expect(root).not.toMatchRenderedOutput('AB');
14941487

14951488
await act(() => resolveFakeImport(ChildA));
1496-
assertLog([
1497-
'A',
1498-
// pre-warming
1499-
'A',
1500-
]);
1489+
assertLog(['A']);
15011490

15021491
await act(() => resolveFakeImport(ChildB));
15031492
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);

0 commit comments

Comments
 (0)