Refactor Lazy Components to use teh Suspense (and wrap Blocks in Lazy) (#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
Sebastian Markbåge committed
Mar 22, 2020 at 21:53 UTC
fd61f7ea53989a59bc427603798bb111c852816a
12 files changed
+291
-342
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+1
-1
@@ -709,7 +709,7 @@ describe('ReactDOMServer', () => {
709
),
710
);
711
ReactDOMServer.renderToString(<LazyFoo />);
712
- }).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
712
+ }).toThrow('ReactDOMServer does not yet support Suspense.');
713
});
714
715
it('throws when suspending on the server', () => {
packages/react-dom/src/server/ReactPartialRenderer.js
+23
-34
@@ -17,8 +17,6 @@ import invariant from 'shared/invariant';
17
import getComponentName from 'shared/getComponentName';
18
import describeComponentFrame from 'shared/describeComponentFrame';
19
import ReactSharedInternals from 'shared/ReactSharedInternals';
20
-import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
21
-import {Resolved, Rejected, Pending} from 'shared/ReactLazyStatusTags';
20
import {
21
warnAboutDeprecatedLifecycles,
22
disableLegacyContext,
@@ -1233,42 +1231,33 @@ class ReactDOMServerRenderer {
1231
// eslint-disable-next-line-no-fallthrough
1232
case REACT_LAZY_TYPE: {
1233
const element: ReactElement = (nextChild: any);
1236
- const lazyComponent: LazyComponent<any> = (nextChild: any).type;
1234
+ const lazyComponent: LazyComponent<any, any> = (nextChild: any)
1235
+ .type;
1236
// Attempt to initialize lazy component regardless of whether the
1237
// suspense server-side renderer is enabled so synchronously
1238
// 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 = [];
1258
}
1259
+ this.stack.push(frame);
1260
+ return '';
1261
}
1262
// eslint-disable-next-line-no-fallthrough
1263
case REACT_SCOPE_TYPE: {
packages/react-reconciler/src/ReactChildFiber.js
+35
-15
@@ -10,6 +10,7 @@
10
import type {ReactElement} from 'shared/ReactElementType';
11
import type {ReactPortal} from 'shared/ReactTypes';
12
import type {BlockComponent} from 'react/src/ReactBlock';
13
+import type {LazyComponent} from 'react/src/ReactLazy';
14
import type {Fiber} from './ReactFiber';
15
import type {ExpirationTime} from './ReactFiberExpirationTime';
16
@@ -20,6 +21,7 @@ import {
21
REACT_ELEMENT_TYPE,
22
REACT_FRAGMENT_TYPE,
23
REACT_PORTAL_TYPE,
24
+ REACT_LAZY_TYPE,
25
REACT_BLOCK_TYPE,
26
} from 'shared/ReactSymbols';
27
import {
@@ -48,7 +50,6 @@ import {
50
} from './ReactCurrentFiber';
51
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
52
import {StrictMode} from './ReactTypeOfMode';
51
-import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
53
54
let didWarnAboutMaps;
55
let didWarnAboutGenerators;
@@ -263,6 +264,22 @@ function warnOnFunctionType() {
264
}
265
}
266
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
+
283
// This wrapper function exists because I expect to clone the code in each path
284
// to be able to optimize each path individually by branching early. This needs
285
// a compiler or we can do it manually. Helpers that don't need this branching
@@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) {
436
existing._debugOwner = element._owner;
437
}
438
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) {
440
// The new Block might not be initialized yet. We need to initialize
441
// 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
+ }
446
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
450
) {
451
// Same as above but also update the .type field.
452
const existing = useFiber(current, element.props);
453
existing.return = returnFiber;
437
- existing.type = element.type;
454
+ existing.type = type;
455
if (__DEV__) {
456
existing._debugSource = element._source;
457
existing._debugOwner = element._owner;
@@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) {
1205
}
1206
case Block:
1207
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) {
1213
// The new Block might not be initialized yet. We need to initialize
1214
// it in case initializing it turns out it would match.
1194
- initializeBlockComponentType(element.type);
1215
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
1218
) {
1219
deleteRemainingChildren(returnFiber, child.sibling);
1220
const existing = useFiber(child, element.props);
1201
- existing.type = element.type;
1221
+ existing.type = type;
1222
existing.return = returnFiber;
1223
if (__DEV__) {
1224
existing._debugSource = element._source;
packages/react-reconciler/src/ReactFiberBeginWork.js
+17
-17
@@ -9,6 +9,7 @@
9
10
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
11
import type {BlockComponent} from 'react/src/ReactBlock';
12
+import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
13
import type {Fiber} from './ReactFiber';
14
import type {FiberRoot} from './ReactFiberRoot';
15
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -73,7 +74,6 @@ import invariant from 'shared/invariant';
74
import shallowEqual from 'shared/shallowEqual';
75
import getComponentName from 'shared/getComponentName';
76
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
76
-import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
77
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
78
import {
79
getCurrentFiberOwnerNameInDevOrNull,
@@ -164,11 +164,7 @@ import {
164
resumeMountClassInstance,
165
updateClassInstance,
166
} from './ReactFiberClassComponent';
167
-import {
168
- readLazyComponentType,
169
- resolveDefaultProps,
170
-} from './ReactFiberLazyComponent';
171
-import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
167
+import {resolveDefaultProps} from './ReactFiberLazyComponent';
168
import {
169
resolveLazyComponentTag,
170
createFiberFromTypeAndProps,
@@ -184,7 +180,6 @@ import {
180
renderDidSuspendDelayIfPossible,
181
markUnprocessedUpdateTime,
182
} from './ReactFiberWorkLoop';
187
-import {Resolved} from 'shared/ReactLazyStatusTags';
183
184
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
185
@@ -492,7 +487,14 @@ function updateSimpleMemoComponent(
487
// We warn when you define propTypes on lazy()
488
// so let's just skip over it to find memo() outer wrapper.
489
// 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
+ }
498
}
499
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
500
if (outerPropTypes) {
@@ -703,10 +705,10 @@ function updateFunctionComponent(
705
return workInProgress.child;
706
}
707
706
-function updateBlock<Props, Payload, Data>(
708
+function updateBlock<Props, Data>(
709
current: Fiber | null,
710
workInProgress: Fiber,
709
- block: BlockComponent<Props, Payload, Data>,
711
+ block: BlockComponent<Props, Data>,
712
nextProps: any,
713
renderExpirationTime: ExpirationTime,
714
) {
@@ -714,12 +716,7 @@ function updateBlock<Props, Payload, Data>(
716
// hasn't yet mounted. This happens after the first render suspends.
717
// We'll need to figure out if this is fine or can cause issues.
718
717
- initializeBlockComponentType(block);
718
- if (block._status !== Resolved) {
719
- throw block._data;
720
- }
721
-
722
- const render = block._fn;
719
+ const render = block._render;
720
const data = block._data;
721
722
// The rest is a fork of updateFunctionComponent
@@ -1142,7 +1139,10 @@ function mountLazyComponent(
1139
// We can't start a User Timing measurement with correct label yet.
1140
// Cancel and resume right after we know the tag.
1141
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);
1146
// Store the unwrapped component in the type.
1147
workInProgress.type = Component;
1148
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
packages/react-reconciler/src/ReactFiberLazyComponent.js
-13
@@ -7,11 +7,6 @@
7
* @flow
8
*/
9
10
-import type {LazyComponent} from 'react/src/ReactLazy';
11
-
12
-import {Resolved} from 'shared/ReactLazyStatusTags';
13
-import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
14
-
10
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
11
if (Component && Component.defaultProps) {
12
// Resolve default props. Taken from ReactElement
@@ -26,11 +21,3 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
21
}
22
return baseProps;
23
}
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
-}
packages/react-reconciler/src/__tests__/ReactBlocks-test.js
+85
-46
@@ -14,11 +14,13 @@ let useState;
14
let Suspense;
15
let block;
16
let readString;
17
+let Scheduler;
18
19
describe('ReactBlocks', () => {
20
beforeEach(() => {
21
jest.resetModules();
22
23
+ Scheduler = require('scheduler');
24
React = require('react');
25
ReactNoop = require('react-noop-renderer');
26
@@ -47,46 +49,44 @@ describe('ReactBlocks', () => {
49
};
50
});
51
50
- it.experimental('renders a component with a suspending query', async () => {
51
- function Query(id) {
52
+ it.experimental('prints the name of the render function in warnings', () => {
53
+ function Query(firstName) {
54
return {
53
- id: id,
54
- name: readString('Sebastian'),
55
+ name: firstName,
56
};
57
}
58
58
- function Render(props, data) {
59
- return (
60
- <span>
61
- {props.title}: {data.name}
62
- </span>
63
- );
59
+ function User(props, data) {
60
+ let array = [<span>{data.name}</span>];
61
+ return <div>{array}</div>;
62
}
63
66
- let loadUser = block(Query, Render);
67
-
68
- function App({User}) {
64
+ function App({Component}) {
65
return (
66
<Suspense fallback={'Loading...'}>
71
- <User title="Name" />
67
+ <Component name="Name" />
68
</Suspense>
69
);
70
}
71
76
- await ReactNoop.act(async () => {
77
- ReactNoop.render(<App User={loadUser(123)} />);
78
- });
72
+ let loadUser = block(Query, User);
73
80
- expect(ReactNoop).toMatchRenderedOutput('Loading...');
81
-
82
- await ReactNoop.act(async () => {
83
- jest.advanceTimersByTime(1000);
84
- });
85
-
86
- expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
74
+ expect(() => {
75
+ ReactNoop.act(() => {
76
+ ReactNoop.render(<App Component={loadUser()} />);
77
+ });
78
+ }).toErrorDev(
79
+ 'Warning: Each child in a list should have a unique ' +
80
+ '"key" prop.\n\nCheck the render method of `User`. See ' +
81
+ 'https://fb.me/react-warning-keys for more information.\n' +
82
+ ' in span (at **)\n' +
83
+ ' in User (at **)\n' +
84
+ ' in Suspense (at **)\n' +
85
+ ' in App (at **)',
86
+ );
87
});
88
89
- it.experimental('supports a lazy wrapper around a chunk', async () => {
89
+ it.experimental('renders a component with a suspending query', async () => {
90
function Query(id) {
91
return {
92
id: id,
@@ -112,30 +112,10 @@ describe('ReactBlocks', () => {
112
);
113
}
114
115
- let resolveLazy;
116
- let LazyUser = React.lazy(
117
- () =>
118
- new Promise(resolve => {
119
- resolveLazy = function() {
120
- resolve({
121
- default: loadUser(123),
122
- });
123
- };
124
- }),
125
- );
126
-
127
- await ReactNoop.act(async () => {
128
- ReactNoop.render(<App User={LazyUser} />);
129
- });
130
-
131
- expect(ReactNoop).toMatchRenderedOutput('Loading...');
132
-
133
- // Resolve the component.
115
await ReactNoop.act(async () => {
135
- await resolveLazy();
116
+ ReactNoop.render(<App User={loadUser(123)} />);
117
});
118
138
- // We're still waiting on the data.
119
expect(ReactNoop).toMatchRenderedOutput('Loading...');
120
121
await ReactNoop.act(async () => {
@@ -145,6 +125,65 @@ describe('ReactBlocks', () => {
125
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
126
});
127
128
+ it.experimental(
129
+ 'does not support a lazy wrapper around a chunk',
130
+ async () => {
131
+ function Query(id) {
132
+ return {
133
+ id: id,
134
+ name: readString('Sebastian'),
135
+ };
136
+ }
137
+
138
+ function Render(props, data) {
139
+ return (
140
+ <span>
141
+ {props.title}: {data.name}
142
+ </span>
143
+ );
144
+ }
145
+
146
+ let loadUser = block(Query, Render);
147
+
148
+ function App({User}) {
149
+ return (
150
+ <Suspense fallback={'Loading...'}>
151
+ <User title="Name" />
152
+ </Suspense>
153
+ );
154
+ }
155
+
156
+ let resolveLazy;
157
+ let LazyUser = React.lazy(
158
+ () =>
159
+ new Promise(resolve => {
160
+ resolveLazy = function() {
161
+ resolve({
162
+ default: loadUser(123),
163
+ });
164
+ };
165
+ }),
166
+ );
167
+
168
+ await ReactNoop.act(async () => {
169
+ ReactNoop.render(<App User={LazyUser} />);
170
+ });
171
+
172
+ expect(ReactNoop).toMatchRenderedOutput('Loading...');
173
+
174
+ // Resolve the component.
175
+ await resolveLazy();
176
+
177
+ expect(Scheduler).toFlushAndThrow(
178
+ 'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
179
+ 'Lazy element type must resolve to a class or function.' +
180
+ (__DEV__
181
+ ? ' Did you wrap a component in React.lazy() more than once?'
182
+ : ''),
183
+ );
184
+ },
185
+ );
186
+
187
it.experimental(
188
'can receive updated data for the same component',
189
async () => {
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+7
-1
@@ -879,7 +879,13 @@ describe('ReactLazy', () => {
879
},
880
);
881
882
- expect(Scheduler).toFlushAndYield(['Started loading', 'Loading...']);
882
+ if (__DEV__) {
883
+ // Getting the name for the warning cause the loading to start early.
884
+ expect(Scheduler).toHaveYielded(['Started loading']);
885
+ expect(Scheduler).toFlushAndYield(['Loading...']);
886
+ } else {
887
+ expect(Scheduler).toFlushAndYield(['Started loading', 'Loading...']);
888
+ }
889
expect(root).not.toMatchRenderedOutput(<div>AB</div>);
890
891
await Promise.resolve();
packages/react/src/ReactBlock.js
+36
-48
@@ -7,7 +7,10 @@
7
* @flow
8
*/
9
10
+import type {LazyComponent} from './ReactLazy';
11
+
12
import {
13
+ REACT_LAZY_TYPE,
14
REACT_BLOCK_TYPE,
15
REACT_MEMO_TYPE,
16
REACT_FORWARD_REF_TYPE,
@@ -19,55 +22,33 @@ type BlockRenderFunction<Props, Data> = (
22
data: Data,
23
) => React$Node;
24
22
-type Thenable<T, R> = {
23
- then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
24
-};
25
-
26
-type Initializer<Props, Payload, Data> = (
27
- payload: Payload,
28
-) =>
29
- | [Data, BlockRenderFunction<Props, Data>]
30
- | Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>;
31
-
32
-export type UninitializedBlockComponent<Props, Payload, Data> = {
33
- $$typeof: Symbol | number,
34
- _status: -1,
35
- _data: Payload,
36
- _fn: Initializer<Props, Payload, Data>,
37
-};
38
-
39
-export type PendingBlockComponent<Props, Data> = {
40
- $$typeof: Symbol | number,
41
- _status: 0,
42
- _data: Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>,
43
- _fn: null,
25
+type Payload<Props, Args: Iterable<any>, Data> = {
26
+ query: BlockQueryFunction<Args, Data>,
27
+ args: Args,
28
+ render: BlockRenderFunction<Props, Data>,
29
};
30
46
-export type ResolvedBlockComponent<Props, Data> = {
31
+export type BlockComponent<Props, Data> = {
32
$$typeof: Symbol | number,
48
- _status: 1,
33
_data: Data,
50
- _fn: BlockRenderFunction<Props, Data>,
34
+ _render: BlockRenderFunction<Props, Data>,
35
};
36
53
-export type RejectedBlockComponent = {
54
- $$typeof: Symbol | number,
55
- _status: 2,
56
- _data: mixed,
57
- _fn: null,
58
-};
59
-
60
-export type BlockComponent<Props, Payload, Data> =
61
- | UninitializedBlockComponent<Props, Payload, Data>
62
- | PendingBlockComponent<Props, Data>
63
- | ResolvedBlockComponent<Props, Data>
64
- | RejectedBlockComponent;
65
-
37
opaque type Block<Props>: React$AbstractComponent<
38
Props,
39
null,
40
> = React$AbstractComponent<Props, null>;
41
42
+function lazyInitializer<Props, Args: Iterable<any>, Data>(
43
+ payload: Payload<Props, Args, Data>,
44
+): BlockComponent<Props, Data> {
45
+ return {
46
+ $$typeof: REACT_BLOCK_TYPE,
47
+ _data: payload.query.apply(null, payload.args),
48
+ _render: payload.render,
49
+ };
50
+}
51
+
52
export function block<Args: Iterable<any>, Props, Data>(
53
query: BlockQueryFunction<Args, Data>,
54
render: BlockRenderFunction<Props, Data>,
@@ -115,19 +96,26 @@ export function block<Args: Iterable<any>, Props, Data>(
96
);
97
}
98
}
118
- function initializer(args) {
119
- let data = query.apply(null, args);
120
- return [data, render];
121
- }
99
+
100
return function(): Block<Props> {
101
let args: Args = arguments;
124
- let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
125
- $$typeof: REACT_BLOCK_TYPE,
126
- _status: -1,
127
- _data: args,
128
- _fn: initializer,
102
+
103
+ let payload: Payload<Props, Args, Data> = {
104
+ query: query,
105
+ args: args,
106
+ render: render,
107
};
108
+
109
+ let lazyType: LazyComponent<
110
+ BlockComponent<Props, Data>,
111
+ Payload<Props, Args, Data>,
112
+ > = {
113
+ $$typeof: REACT_LAZY_TYPE,
114
+ _payload: payload,
115
+ _init: lazyInitializer,
116
+ };
117
+
118
// $FlowFixMe
131
- return blockComponent;
119
+ return lazyType;
120
};
121
}
packages/react/src/ReactLazy.js
+79
-20
@@ -13,46 +13,105 @@ type Thenable<T, R> = {
13
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
14
};
15
16
-export type UninitializedLazyComponent<T> = {
17
- $$typeof: Symbol | number,
16
+const Uninitialized = -1;
17
+const Pending = 0;
18
+const Resolved = 1;
19
+const Rejected = 2;
20
+
21
+type UninitializedPayload<T> = {
22
_status: -1,
19
- _result: () => Thenable<{default: T, ...} | T, mixed>,
23
+ _result: () => Thenable<{default: T, ...}, mixed>,
24
};
25
22
-export type PendingLazyComponent<T> = {
23
- $$typeof: Symbol | number,
26
+type PendingPayload<T> = {
27
_status: 0,
25
- _result: Thenable<{default: T, ...} | T, mixed>,
28
+ _result: Thenable<{default: T, ...}, mixed>,
29
};
30
28
-export type ResolvedLazyComponent<T> = {
29
- $$typeof: Symbol | number,
31
+type ResolvedPayload<T> = {
32
_status: 1,
33
_result: T,
34
};
35
34
-export type RejectedLazyComponent = {
35
- $$typeof: Symbol | number,
36
+type RejectedPayload = {
37
_status: 2,
38
_result: mixed,
39
};
40
40
-export type LazyComponent<T> =
41
- | UninitializedLazyComponent<T>
42
- | PendingLazyComponent<T>
43
- | ResolvedLazyComponent<T>
44
- | RejectedLazyComponent;
41
+type Payload<T> =
42
+ | UninitializedPayload<T>
43
+ | PendingPayload<T>
44
+ | ResolvedPayload<T>
45
+ | RejectedPayload;
46
+
47
+export type LazyComponent<T, P> = {
48
+ $$typeof: Symbol | number,
49
+ _payload: P,
50
+ _init: (payload: P) => T,
51
+};
52
+
53
+function lazyInitializer<T>(payload: Payload<T>): T {
54
+ if (payload._status === Uninitialized) {
55
+ const ctor = payload._result;
56
+ const thenable = ctor();
57
+ // Transition to the next state.
58
+ const pending: PendingPayload<any> = (payload: any);
59
+ pending._status = Pending;
60
+ pending._result = thenable;
61
+ thenable.then(
62
+ moduleObject => {
63
+ if (payload._status === Pending) {
64
+ const defaultExport = moduleObject.default;
65
+ if (__DEV__) {
66
+ if (defaultExport === undefined) {
67
+ console.error(
68
+ 'lazy: Expected the result of a dynamic import() call. ' +
69
+ 'Instead received: %s\n\nYour code should look like: \n ' +
70
+ // Break up imports to avoid accidentally parsing them as dependencies.
71
+ 'const MyComponent = lazy(() => imp' +
72
+ "ort('./MyComponent'))",
73
+ moduleObject,
74
+ );
75
+ }
76
+ }
77
+ // Transition to the next state.
78
+ const resolved: ResolvedPayload<any> = (payload: any);
79
+ resolved._status = Resolved;
80
+ resolved._result = defaultExport;
81
+ }
82
+ },
83
+ error => {
84
+ if (payload._status === Pending) {
85
+ // Transition to the next state.
86
+ const rejected: RejectedPayload = (payload: any);
87
+ rejected._status = Rejected;
88
+ rejected._result = error;
89
+ }
90
+ },
91
+ );
92
+ }
93
+ if (payload._status === Resolved) {
94
+ return payload._result;
95
+ } else {
96
+ throw payload._result;
97
+ }
98
+}
99
100
export function lazy<T>(
47
- ctor: () => Thenable<{default: T, ...} | T, mixed>,
48
-): LazyComponent<T> {
49
- let lazyType: LazyComponent<T> = {
50
- $$typeof: REACT_LAZY_TYPE,
51
- // React uses these fields to store the result.
101
+ ctor: () => Thenable<{default: T, ...}, mixed>,
102
+): LazyComponent<T, Payload<T>> {
103
+ let payload: Payload<T> = {
104
+ // We use these fields to store the result.
105
_status: -1,
106
_result: ctor,
107
};
108
109
+ let lazyType: LazyComponent<T, Payload<T>> = {
110
+ $$typeof: REACT_LAZY_TYPE,
111
+ _payload: payload,
112
+ _init: lazyInitializer,
113
+ };
114
+
115
if (__DEV__) {
116
// In production, this would just set it on the object.
117
let defaultProps;
packages/shared/ReactLazyComponent.js
deleted
-126
@@ -1,126 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-import type {
11
- PendingLazyComponent,
12
- ResolvedLazyComponent,
13
- RejectedLazyComponent,
14
- LazyComponent,
15
-} from 'react/src/ReactLazy';
16
-
17
-import type {
18
- PendingBlockComponent,
19
- ResolvedBlockComponent,
20
- RejectedBlockComponent,
21
- BlockComponent,
22
-} from 'react/src/ReactBlock';
23
-
24
-import {
25
- Uninitialized,
26
- Pending,
27
- Resolved,
28
- Rejected,
29
-} from './ReactLazyStatusTags';
30
-
31
-export function refineResolvedLazyComponent<T>(
32
- lazyComponent: LazyComponent<T>,
33
-): T | null {
34
- return lazyComponent._status === Resolved ? lazyComponent._result : null;
35
-}
36
-
37
-export function initializeLazyComponentType(
38
- lazyComponent: LazyComponent<any>,
39
-): void {
40
- if (lazyComponent._status === Uninitialized) {
41
- const ctor = lazyComponent._result;
42
- const thenable = ctor();
43
- // Transition to the next state.
44
- const pending: PendingLazyComponent<any> = (lazyComponent: any);
45
- pending._status = Pending;
46
- pending._result = thenable;
47
- thenable.then(
48
- moduleObject => {
49
- if (lazyComponent._status === Pending) {
50
- const defaultExport = moduleObject.default;
51
- if (__DEV__) {
52
- if (defaultExport === undefined) {
53
- console.error(
54
- 'lazy: Expected the result of a dynamic import() call. ' +
55
- 'Instead received: %s\n\nYour code should look like: \n ' +
56
- // Break up imports to avoid accidentally parsing them as dependencies.
57
- 'const MyComponent = lazy(() => imp' +
58
- "ort('./MyComponent'))",
59
- moduleObject,
60
- );
61
- }
62
- }
63
- // Transition to the next state.
64
- const resolved: ResolvedLazyComponent<any> = (lazyComponent: any);
65
- resolved._status = Resolved;
66
- resolved._result = defaultExport;
67
- }
68
- },
69
- error => {
70
- if (lazyComponent._status === Pending) {
71
- // Transition to the next state.
72
- const rejected: RejectedLazyComponent = (lazyComponent: any);
73
- rejected._status = Rejected;
74
- rejected._result = error;
75
- }
76
- },
77
- );
78
- }
79
-}
80
-
81
-export function initializeBlockComponentType<Props, Payload, Data>(
82
- blockComponent: BlockComponent<Props, Payload, Data>,
83
-): void {
84
- if (blockComponent._status === Uninitialized) {
85
- const thenableOrTuple = blockComponent._fn(blockComponent._data);
86
- if (typeof thenableOrTuple.then !== 'function') {
87
- let tuple: [any, any] = (thenableOrTuple: any);
88
- const resolved: ResolvedBlockComponent<
89
- Props,
90
- Data,
91
- > = (blockComponent: any);
92
- resolved._status = Resolved;
93
- resolved._data = tuple[0];
94
- resolved._fn = tuple[1];
95
- return;
96
- }
97
- const thenable = (thenableOrTuple: any);
98
- // Transition to the next state.
99
- const pending: PendingBlockComponent<Props, Data> = (blockComponent: any);
100
- pending._status = Pending;
101
- pending._data = thenable;
102
- pending._fn = null;
103
- thenable.then(
104
- (tuple: [any, any]) => {
105
- if (blockComponent._status === Pending) {
106
- // Transition to the next state.
107
- const resolved: ResolvedBlockComponent<
108
- Props,
109
- Data,
110
- > = (blockComponent: any);
111
- resolved._status = Resolved;
112
- resolved._data = tuple[0];
113
- resolved._fn = tuple[1];
114
- }
115
- },
116
- error => {
117
- if (blockComponent._status === Pending) {
118
- // Transition to the next state.
119
- const rejected: RejectedBlockComponent = (blockComponent: any);
120
- rejected._status = Rejected;
121
- rejected._data = error;
122
- }
123
- },
124
- );
125
- }
126
-}
packages/shared/ReactLazyStatusTags.js
deleted
-14
@@ -1,14 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @flow
8
- */
9
-
10
-// TODO: Move this to "react" once we can import from externals.
11
-export const Uninitialized = -1;
12
-export const Pending = 0;
13
-export const Resolved = 1;
14
-export const Rejected = 2;
packages/shared/getComponentName.js
+8
-7
@@ -23,7 +23,6 @@ import {
23
REACT_LAZY_TYPE,
24
REACT_BLOCK_TYPE,
25
} from 'shared/ReactSymbols';
26
-import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
26
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
27
28
function getWrappedName(
@@ -88,14 +87,16 @@ function getComponentName(type: mixed): string | null {
87
case REACT_MEMO_TYPE:
88
return getComponentName(type.type);
89
case REACT_BLOCK_TYPE:
91
- return getComponentName(type.render);
90
+ return getComponentName(type._render);
91
case REACT_LAZY_TYPE: {
93
- const thenable: LazyComponent<mixed> = (type: any);
94
- const resolvedThenable = refineResolvedLazyComponent(thenable);
95
- if (resolvedThenable) {
96
- return getComponentName(resolvedThenable);
92
+ const lazyComponent: LazyComponent<any, any> = (type: any);
93
+ let payload = lazyComponent._payload;
94
+ let init = lazyComponent._init;
95
+ try {
96
+ return getComponentName(init(payload));
97
+ } catch (x) {
98
+ return null;
99
}
98
- break;
100
}
101
}
102
}