Skip to content

Commit fd61f7e

Browse files
authored
Refactor Lazy Components to use teh Suspense (and wrap Blocks in Lazy) (facebook#18362)
* Refactor Lazy Components * Switch Blocks to using a Lazy component wrapper Then resolve to a true Block inside. * Test component names of lazy Blocks
1 parent 31a9e39 commit fd61f7e

File tree

12 files changed

+291
-342
lines changed

12 files changed

+291
-342
lines changed

packages/react-dom/src/__tests__/ReactServerRendering-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ describe('ReactDOMServer', () => {
709709
),
710710
);
711711
ReactDOMServer.renderToString(<LazyFoo />);
712-
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
712+
}).toThrow('ReactDOMServer does not yet support Suspense.');
713713
});
714714

715715
it('throws when suspending on the server', () => {

packages/react-dom/src/server/ReactPartialRenderer.js

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import invariant from 'shared/invariant';
1717
import getComponentName from 'shared/getComponentName';
1818
import describeComponentFrame from 'shared/describeComponentFrame';
1919
import ReactSharedInternals from 'shared/ReactSharedInternals';
20-
import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
21-
import {Resolved, Rejected, Pending} from 'shared/ReactLazyStatusTags';
2220
import {
2321
warnAboutDeprecatedLifecycles,
2422
disableLegacyContext,
@@ -1233,42 +1231,33 @@ class ReactDOMServerRenderer {
12331231
// eslint-disable-next-line-no-fallthrough
12341232
case REACT_LAZY_TYPE: {
12351233
const element: ReactElement = (nextChild: any);
1236-
const lazyComponent: LazyComponent<any> = (nextChild: any).type;
1234+
const lazyComponent: LazyComponent<any, any> = (nextChild: any)
1235+
.type;
12371236
// Attempt to initialize lazy component regardless of whether the
12381237
// suspense server-side renderer is enabled so synchronously
12391238
// resolved constructors are supported.
1240-
initializeLazyComponentType(lazyComponent);
1241-
switch (lazyComponent._status) {
1242-
case Resolved: {
1243-
const nextChildren = [
1244-
React.createElement(
1245-
lazyComponent._result,
1246-
Object.assign({ref: element.ref}, element.props),
1247-
),
1248-
];
1249-
const frame: Frame = {
1250-
type: null,
1251-
domNamespace: parentNamespace,
1252-
children: nextChildren,
1253-
childIndex: 0,
1254-
context: context,
1255-
footer: '',
1256-
};
1257-
if (__DEV__) {
1258-
((frame: any): FrameDev).debugElementStack = [];
1259-
}
1260-
this.stack.push(frame);
1261-
return '';
1262-
}
1263-
case Rejected:
1264-
throw lazyComponent._result;
1265-
case Pending:
1266-
default:
1267-
invariant(
1268-
false,
1269-
'ReactDOMServer does not yet support lazy-loaded components.',
1270-
);
1239+
let payload = lazyComponent._payload;
1240+
let init = lazyComponent._init;
1241+
let result = init(payload);
1242+
const nextChildren = [
1243+
React.createElement(
1244+
result,
1245+
Object.assign({ref: element.ref}, element.props),
1246+
),
1247+
];
1248+
const frame: Frame = {
1249+
type: null,
1250+
domNamespace: parentNamespace,
1251+
children: nextChildren,
1252+
childIndex: 0,
1253+
context: context,
1254+
footer: '',
1255+
};
1256+
if (__DEV__) {
1257+
((frame: any): FrameDev).debugElementStack = [];
12711258
}
1259+
this.stack.push(frame);
1260+
return '';
12721261
}
12731262
// eslint-disable-next-line-no-fallthrough
12741263
case REACT_SCOPE_TYPE: {

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {ReactElement} from 'shared/ReactElementType';
1111
import type {ReactPortal} from 'shared/ReactTypes';
1212
import type {BlockComponent} from 'react/src/ReactBlock';
13+
import type {LazyComponent} from 'react/src/ReactLazy';
1314
import type {Fiber} from './ReactFiber';
1415
import type {ExpirationTime} from './ReactFiberExpirationTime';
1516

@@ -20,6 +21,7 @@ import {
2021
REACT_ELEMENT_TYPE,
2122
REACT_FRAGMENT_TYPE,
2223
REACT_PORTAL_TYPE,
24+
REACT_LAZY_TYPE,
2325
REACT_BLOCK_TYPE,
2426
} from 'shared/ReactSymbols';
2527
import {
@@ -48,7 +50,6 @@ import {
4850
} from './ReactCurrentFiber';
4951
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
5052
import {StrictMode} from './ReactTypeOfMode';
51-
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
5253

5354
let didWarnAboutMaps;
5455
let didWarnAboutGenerators;
@@ -263,6 +264,22 @@ function warnOnFunctionType() {
263264
}
264265
}
265266

267+
// We avoid inlining this to avoid potential deopts from using try/catch.
268+
/** @noinline */
269+
function resolveLazyType<T, P>(
270+
lazyComponent: LazyComponent<T, P>,
271+
): LazyComponent<T, P> | T {
272+
try {
273+
// If we can, let's peek at the resulting type.
274+
let payload = lazyComponent._payload;
275+
let init = lazyComponent._init;
276+
return init(payload);
277+
} catch (x) {
278+
// Leave it in place and let it throw again in the begin phase.
279+
return lazyComponent;
280+
}
281+
}
282+
266283
// This wrapper function exists because I expect to clone the code in each path
267284
// to be able to optimize each path individually by branching early. This needs
268285
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) {
419436
existing._debugOwner = element._owner;
420437
}
421438
return existing;
422-
} else if (
423-
enableBlocksAPI &&
424-
current.tag === Block &&
425-
element.type.$$typeof === REACT_BLOCK_TYPE
426-
) {
439+
} else if (enableBlocksAPI && current.tag === Block) {
427440
// The new Block might not be initialized yet. We need to initialize
428441
// it in case initializing it turns out it would match.
429-
initializeBlockComponentType(element.type);
442+
let type = element.type;
443+
if (type.$$typeof === REACT_LAZY_TYPE) {
444+
type = resolveLazyType(type);
445+
}
430446
if (
431-
(element.type: BlockComponent<any, any, any>)._fn ===
432-
(current.type: BlockComponent<any, any, any>)._fn
447+
type.$$typeof === REACT_BLOCK_TYPE &&
448+
((type: any): BlockComponent<any, any>)._render ===
449+
(current.type: BlockComponent<any, any>)._render
433450
) {
434451
// Same as above but also update the .type field.
435452
const existing = useFiber(current, element.props);
436453
existing.return = returnFiber;
437-
existing.type = element.type;
454+
existing.type = type;
438455
if (__DEV__) {
439456
existing._debugSource = element._source;
440457
existing._debugOwner = element._owner;
@@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) {
11881205
}
11891206
case Block:
11901207
if (enableBlocksAPI) {
1191-
if (element.type.$$typeof === REACT_BLOCK_TYPE) {
1208+
let type = element.type;
1209+
if (type.$$typeof === REACT_LAZY_TYPE) {
1210+
type = resolveLazyType(type);
1211+
}
1212+
if (type.$$typeof === REACT_BLOCK_TYPE) {
11921213
// The new Block might not be initialized yet. We need to initialize
11931214
// it in case initializing it turns out it would match.
1194-
initializeBlockComponentType(element.type);
11951215
if (
1196-
(element.type: BlockComponent<any, any, any>)._fn ===
1197-
(child.type: BlockComponent<any, any, any>)._fn
1216+
((type: any): BlockComponent<any, any>)._render ===
1217+
(child.type: BlockComponent<any, any>)._render
11981218
) {
11991219
deleteRemainingChildren(returnFiber, child.sibling);
12001220
const existing = useFiber(child, element.props);
1201-
existing.type = element.type;
1221+
existing.type = type;
12021222
existing.return = returnFiber;
12031223
if (__DEV__) {
12041224
existing._debugSource = element._source;

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
1111
import type {BlockComponent} from 'react/src/ReactBlock';
12+
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
1213
import type {Fiber} from './ReactFiber';
1314
import type {FiberRoot} from './ReactFiberRoot';
1415
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -73,7 +74,6 @@ import invariant from 'shared/invariant';
7374
import shallowEqual from 'shared/shallowEqual';
7475
import getComponentName from 'shared/getComponentName';
7576
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
76-
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
7777
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
7878
import {
7979
getCurrentFiberOwnerNameInDevOrNull,
@@ -164,11 +164,7 @@ import {
164164
resumeMountClassInstance,
165165
updateClassInstance,
166166
} from './ReactFiberClassComponent';
167-
import {
168-
readLazyComponentType,
169-
resolveDefaultProps,
170-
} from './ReactFiberLazyComponent';
171-
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
167+
import {resolveDefaultProps} from './ReactFiberLazyComponent';
172168
import {
173169
resolveLazyComponentTag,
174170
createFiberFromTypeAndProps,
@@ -184,7 +180,6 @@ import {
184180
renderDidSuspendDelayIfPossible,
185181
markUnprocessedUpdateTime,
186182
} from './ReactFiberWorkLoop';
187-
import {Resolved} from 'shared/ReactLazyStatusTags';
188183

189184
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
190185

@@ -492,7 +487,14 @@ function updateSimpleMemoComponent(
492487
// We warn when you define propTypes on lazy()
493488
// so let's just skip over it to find memo() outer wrapper.
494489
// Inner props for memo are validated later.
495-
outerMemoType = refineResolvedLazyComponent(outerMemoType);
490+
const lazyComponent: LazyComponentType<any, any> = outerMemoType;
491+
let payload = lazyComponent._payload;
492+
let init = lazyComponent._init;
493+
try {
494+
outerMemoType = init(payload);
495+
} catch (x) {
496+
outerMemoType = null;
497+
}
496498
}
497499
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
498500
if (outerPropTypes) {
@@ -703,23 +705,18 @@ function updateFunctionComponent(
703705
return workInProgress.child;
704706
}
705707

706-
function updateBlock<Props, Payload, Data>(
708+
function updateBlock<Props, Data>(
707709
current: Fiber | null,
708710
workInProgress: Fiber,
709-
block: BlockComponent<Props, Payload, Data>,
711+
block: BlockComponent<Props, Data>,
710712
nextProps: any,
711713
renderExpirationTime: ExpirationTime,
712714
) {
713715
// TODO: current can be non-null here even if the component
714716
// hasn't yet mounted. This happens after the first render suspends.
715717
// We'll need to figure out if this is fine or can cause issues.
716718

717-
initializeBlockComponentType(block);
718-
if (block._status !== Resolved) {
719-
throw block._data;
720-
}
721-
722-
const render = block._fn;
719+
const render = block._render;
723720
const data = block._data;
724721

725722
// The rest is a fork of updateFunctionComponent
@@ -1142,7 +1139,10 @@ function mountLazyComponent(
11421139
// We can't start a User Timing measurement with correct label yet.
11431140
// Cancel and resume right after we know the tag.
11441141
cancelWorkTimer(workInProgress);
1145-
let Component = readLazyComponentType(elementType);
1142+
let lazyComponent: LazyComponentType<any, any> = elementType;
1143+
let payload = lazyComponent._payload;
1144+
let init = lazyComponent._init;
1145+
let Component = init(payload);
11461146
// Store the unwrapped component in the type.
11471147
workInProgress.type = Component;
11481148
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));

packages/react-reconciler/src/ReactFiberLazyComponent.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
* @flow
88
*/
99

10-
import type {LazyComponent} from 'react/src/ReactLazy';
11-
12-
import {Resolved} from 'shared/ReactLazyStatusTags';
13-
import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
14-
1510
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
1611
if (Component && Component.defaultProps) {
1712
// Resolve default props. Taken from ReactElement
@@ -26,11 +21,3 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
2621
}
2722
return baseProps;
2823
}
29-
30-
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
31-
initializeLazyComponentType(lazyComponent);
32-
if (lazyComponent._status !== Resolved) {
33-
throw lazyComponent._result;
34-
}
35-
return lazyComponent._result;
36-
}

0 commit comments

Comments
 (0)