Skip to content

Commit 738f23f

Browse files
committed
Lazy components must use React.lazy
Removes support for using arbitrary promises as the type of a React element. Instead, promises must be wrapped in React.lazy. This gives us flexibility later if we need to change the protocol. The reason is that promises do not provide a way to call their constructor multiple times. For example: const promiseForA = new Promise(resolve => { fetchA(a => resolve(a)); }); Given a reference to `promiseForA`, there's no way to call `fetchA` again. Calling `then` on the promise doesn't run the constructor again; it only attaches another listener. In the future we will likely introduce an API like `React.eager` that is similar to `lazy` but eagerly calls the constructor. That gives us the ability to call the constructor multiple times. E.g. to increase the priority, or to retry if the first operation failed.
1 parent 98bab66 commit 738f23f

13 files changed

+386
-353
lines changed

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,6 @@ describe('ReactDOMServer', () => {
582582
);
583583
ReactDOMServer.renderToString(<LazyFoo />);
584584
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
585-
586-
expect(() => {
587-
const FooPromise = {then() {}};
588-
ReactDOMServer.renderToString(<FooPromise />);
589-
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
590585
});
591586

592587
it('should throw (in dev) when children are mutated during render', () => {

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -444,15 +444,18 @@ describe('ReactDOMServerHydration', () => {
444444
});
445445

446446
it('should be able to use lazy components after hydrating', async () => {
447-
const Lazy = new Promise(resolve => {
448-
setTimeout(
449-
() =>
450-
resolve(function World() {
451-
return 'world';
452-
}),
453-
1000,
454-
);
455-
});
447+
const Lazy = React.lazy(
448+
() =>
449+
new Promise(resolve => {
450+
setTimeout(
451+
() =>
452+
resolve(function World() {
453+
return 'world';
454+
}),
455+
1000,
456+
);
457+
}),
458+
);
456459
class HelloWorld extends React.Component {
457460
state = {isClient: false};
458461
componentDidMount() {

packages/react-reconciler/src/ReactFiber.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
REACT_CONCURRENT_MODE_TYPE,
6161
REACT_SUSPENSE_TYPE,
6262
REACT_PURE_TYPE,
63+
REACT_LAZY_TYPE,
6364
} from 'shared/ReactSymbols';
6465

6566
let hasBadMapPolyfill;
@@ -461,12 +462,9 @@ export function createFiberFromElement(
461462
case REACT_PURE_TYPE:
462463
fiberTag = PureComponent;
463464
break getTag;
464-
default: {
465-
if (typeof type.then === 'function') {
466-
fiberTag = IndeterminateComponent;
467-
break getTag;
468-
}
469-
}
465+
case REACT_LAZY_TYPE:
466+
fiberTag = IndeterminateComponent;
467+
break getTag;
470468
}
471469
}
472470
let info = '';

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import {
110110
createFiberFromFragment,
111111
createWorkInProgress,
112112
} from './ReactFiber';
113+
import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
113114

114115
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
115116

@@ -729,7 +730,7 @@ function mountIndeterminateComponent(
729730
if (
730731
typeof Component === 'object' &&
731732
Component !== null &&
732-
typeof Component.then === 'function'
733+
Component.$$typeof === REACT_LAZY_TYPE
733734
) {
734735
// We can't start a User Timing measurement with correct label yet.
735736
// Cancel and resume right after we know the tag.

packages/react-reconciler/src/ReactFiberLazyComponent.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,28 @@
77
* @flow
88
*/
99

10-
import type {Thenable} from 'shared/ReactLazyComponent';
10+
import type {LazyComponentThenable} from 'shared/ReactLazyComponent';
1111

1212
import {Resolved, Rejected, Pending} from 'shared/ReactLazyComponent';
1313

14-
export function readLazyComponentType<T>(thenable: Thenable<T>): T {
15-
const status = thenable._reactStatus;
14+
export function readLazyComponentType<T>(
15+
thenable: LazyComponentThenable<T>,
16+
): T {
17+
const status = thenable._status;
1618
switch (status) {
1719
case Resolved:
18-
const Component: T = thenable._reactResult;
20+
const Component: T = thenable._result;
1921
return Component;
2022
case Rejected:
21-
throw thenable._reactResult;
23+
throw thenable._result;
2224
case Pending:
2325
throw thenable;
2426
default: {
25-
thenable._reactStatus = Pending;
27+
thenable._status = Pending;
2628
thenable.then(
2729
resolvedValue => {
28-
if (thenable._reactStatus === Pending) {
29-
thenable._reactStatus = Resolved;
30+
if (thenable._status === Pending) {
31+
thenable._status = Resolved;
3032
if (typeof resolvedValue === 'object' && resolvedValue !== null) {
3133
// If the `default` property is not empty, assume it's the result
3234
// of an async import() and use that. Otherwise, use the
@@ -39,13 +41,13 @@ export function readLazyComponentType<T>(thenable: Thenable<T>): T {
3941
} else {
4042
resolvedValue = resolvedValue;
4143
}
42-
thenable._reactResult = resolvedValue;
44+
thenable._result = resolvedValue;
4345
}
4446
},
4547
error => {
46-
if (thenable._reactStatus === Pending) {
47-
thenable._reactStatus = Rejected;
48-
thenable._reactResult = error;
48+
if (thenable._status === Pending) {
49+
thenable._status = Rejected;
50+
thenable._result = error;
4951
}
5052
},
5153
);

0 commit comments

Comments
 (0)