@samitouri / QOS-React-2 / commits / fd61f7ea53

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 }