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
This commit is contained in:
parent
31a9e391f7
commit
fd61f7ea53
|
@ -709,7 +709,7 @@ describe('ReactDOMServer', () => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
ReactDOMServer.renderToString(<LazyFoo />);
|
ReactDOMServer.renderToString(<LazyFoo />);
|
||||||
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
|
}).toThrow('ReactDOMServer does not yet support Suspense.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when suspending on the server', () => {
|
it('throws when suspending on the server', () => {
|
||||||
|
|
|
@ -17,8 +17,6 @@ import invariant from 'shared/invariant';
|
||||||
import getComponentName from 'shared/getComponentName';
|
import getComponentName from 'shared/getComponentName';
|
||||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||||
import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
|
|
||||||
import {Resolved, Rejected, Pending} from 'shared/ReactLazyStatusTags';
|
|
||||||
import {
|
import {
|
||||||
warnAboutDeprecatedLifecycles,
|
warnAboutDeprecatedLifecycles,
|
||||||
disableLegacyContext,
|
disableLegacyContext,
|
||||||
|
@ -1233,16 +1231,17 @@ class ReactDOMServerRenderer {
|
||||||
// eslint-disable-next-line-no-fallthrough
|
// eslint-disable-next-line-no-fallthrough
|
||||||
case REACT_LAZY_TYPE: {
|
case REACT_LAZY_TYPE: {
|
||||||
const element: ReactElement = (nextChild: any);
|
const element: ReactElement = (nextChild: any);
|
||||||
const lazyComponent: LazyComponent<any> = (nextChild: any).type;
|
const lazyComponent: LazyComponent<any, any> = (nextChild: any)
|
||||||
|
.type;
|
||||||
// Attempt to initialize lazy component regardless of whether the
|
// Attempt to initialize lazy component regardless of whether the
|
||||||
// suspense server-side renderer is enabled so synchronously
|
// suspense server-side renderer is enabled so synchronously
|
||||||
// resolved constructors are supported.
|
// resolved constructors are supported.
|
||||||
initializeLazyComponentType(lazyComponent);
|
let payload = lazyComponent._payload;
|
||||||
switch (lazyComponent._status) {
|
let init = lazyComponent._init;
|
||||||
case Resolved: {
|
let result = init(payload);
|
||||||
const nextChildren = [
|
const nextChildren = [
|
||||||
React.createElement(
|
React.createElement(
|
||||||
lazyComponent._result,
|
result,
|
||||||
Object.assign({ref: element.ref}, element.props),
|
Object.assign({ref: element.ref}, element.props),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -1260,16 +1259,6 @@ class ReactDOMServerRenderer {
|
||||||
this.stack.push(frame);
|
this.stack.push(frame);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
case Rejected:
|
|
||||||
throw lazyComponent._result;
|
|
||||||
case Pending:
|
|
||||||
default:
|
|
||||||
invariant(
|
|
||||||
false,
|
|
||||||
'ReactDOMServer does not yet support lazy-loaded components.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line-no-fallthrough
|
// eslint-disable-next-line-no-fallthrough
|
||||||
case REACT_SCOPE_TYPE: {
|
case REACT_SCOPE_TYPE: {
|
||||||
if (enableScopeAPI) {
|
if (enableScopeAPI) {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
import type {ReactElement} from 'shared/ReactElementType';
|
import type {ReactElement} from 'shared/ReactElementType';
|
||||||
import type {ReactPortal} from 'shared/ReactTypes';
|
import type {ReactPortal} from 'shared/ReactTypes';
|
||||||
import type {BlockComponent} from 'react/src/ReactBlock';
|
import type {BlockComponent} from 'react/src/ReactBlock';
|
||||||
|
import type {LazyComponent} from 'react/src/ReactLazy';
|
||||||
import type {Fiber} from './ReactFiber';
|
import type {Fiber} from './ReactFiber';
|
||||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ import {
|
||||||
REACT_ELEMENT_TYPE,
|
REACT_ELEMENT_TYPE,
|
||||||
REACT_FRAGMENT_TYPE,
|
REACT_FRAGMENT_TYPE,
|
||||||
REACT_PORTAL_TYPE,
|
REACT_PORTAL_TYPE,
|
||||||
|
REACT_LAZY_TYPE,
|
||||||
REACT_BLOCK_TYPE,
|
REACT_BLOCK_TYPE,
|
||||||
} from 'shared/ReactSymbols';
|
} from 'shared/ReactSymbols';
|
||||||
import {
|
import {
|
||||||
|
@ -48,7 +50,6 @@ import {
|
||||||
} from './ReactCurrentFiber';
|
} from './ReactCurrentFiber';
|
||||||
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
|
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
|
||||||
import {StrictMode} from './ReactTypeOfMode';
|
import {StrictMode} from './ReactTypeOfMode';
|
||||||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
|
|
||||||
|
|
||||||
let didWarnAboutMaps;
|
let didWarnAboutMaps;
|
||||||
let didWarnAboutGenerators;
|
let didWarnAboutGenerators;
|
||||||
|
@ -263,6 +264,22 @@ function warnOnFunctionType() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We avoid inlining this to avoid potential deopts from using try/catch.
|
||||||
|
/** @noinline */
|
||||||
|
function resolveLazyType<T, P>(
|
||||||
|
lazyComponent: LazyComponent<T, P>,
|
||||||
|
): LazyComponent<T, P> | T {
|
||||||
|
try {
|
||||||
|
// If we can, let's peek at the resulting type.
|
||||||
|
let payload = lazyComponent._payload;
|
||||||
|
let init = lazyComponent._init;
|
||||||
|
return init(payload);
|
||||||
|
} catch (x) {
|
||||||
|
// Leave it in place and let it throw again in the begin phase.
|
||||||
|
return lazyComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This wrapper function exists because I expect to clone the code in each path
|
// This wrapper function exists because I expect to clone the code in each path
|
||||||
// to be able to optimize each path individually by branching early. This needs
|
// to be able to optimize each path individually by branching early. This needs
|
||||||
// a compiler or we can do it manually. Helpers that don't need this branching
|
// a compiler or we can do it manually. Helpers that don't need this branching
|
||||||
|
@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) {
|
||||||
existing._debugOwner = element._owner;
|
existing._debugOwner = element._owner;
|
||||||
}
|
}
|
||||||
return existing;
|
return existing;
|
||||||
} else if (
|
} else if (enableBlocksAPI && current.tag === Block) {
|
||||||
enableBlocksAPI &&
|
|
||||||
current.tag === Block &&
|
|
||||||
element.type.$$typeof === REACT_BLOCK_TYPE
|
|
||||||
) {
|
|
||||||
// The new Block might not be initialized yet. We need to initialize
|
// The new Block might not be initialized yet. We need to initialize
|
||||||
// it in case initializing it turns out it would match.
|
// it in case initializing it turns out it would match.
|
||||||
initializeBlockComponentType(element.type);
|
let type = element.type;
|
||||||
|
if (type.$$typeof === REACT_LAZY_TYPE) {
|
||||||
|
type = resolveLazyType(type);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(element.type: BlockComponent<any, any, any>)._fn ===
|
type.$$typeof === REACT_BLOCK_TYPE &&
|
||||||
(current.type: BlockComponent<any, any, any>)._fn
|
((type: any): BlockComponent<any, any>)._render ===
|
||||||
|
(current.type: BlockComponent<any, any>)._render
|
||||||
) {
|
) {
|
||||||
// Same as above but also update the .type field.
|
// Same as above but also update the .type field.
|
||||||
const existing = useFiber(current, element.props);
|
const existing = useFiber(current, element.props);
|
||||||
existing.return = returnFiber;
|
existing.return = returnFiber;
|
||||||
existing.type = element.type;
|
existing.type = type;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
existing._debugSource = element._source;
|
existing._debugSource = element._source;
|
||||||
existing._debugOwner = element._owner;
|
existing._debugOwner = element._owner;
|
||||||
|
@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) {
|
||||||
}
|
}
|
||||||
case Block:
|
case Block:
|
||||||
if (enableBlocksAPI) {
|
if (enableBlocksAPI) {
|
||||||
if (element.type.$$typeof === REACT_BLOCK_TYPE) {
|
let type = element.type;
|
||||||
|
if (type.$$typeof === REACT_LAZY_TYPE) {
|
||||||
|
type = resolveLazyType(type);
|
||||||
|
}
|
||||||
|
if (type.$$typeof === REACT_BLOCK_TYPE) {
|
||||||
// The new Block might not be initialized yet. We need to initialize
|
// The new Block might not be initialized yet. We need to initialize
|
||||||
// it in case initializing it turns out it would match.
|
// it in case initializing it turns out it would match.
|
||||||
initializeBlockComponentType(element.type);
|
|
||||||
if (
|
if (
|
||||||
(element.type: BlockComponent<any, any, any>)._fn ===
|
((type: any): BlockComponent<any, any>)._render ===
|
||||||
(child.type: BlockComponent<any, any, any>)._fn
|
(child.type: BlockComponent<any, any>)._render
|
||||||
) {
|
) {
|
||||||
deleteRemainingChildren(returnFiber, child.sibling);
|
deleteRemainingChildren(returnFiber, child.sibling);
|
||||||
const existing = useFiber(child, element.props);
|
const existing = useFiber(child, element.props);
|
||||||
existing.type = element.type;
|
existing.type = type;
|
||||||
existing.return = returnFiber;
|
existing.return = returnFiber;
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
existing._debugSource = element._source;
|
existing._debugSource = element._source;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
|
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
|
||||||
import type {BlockComponent} from 'react/src/ReactBlock';
|
import type {BlockComponent} from 'react/src/ReactBlock';
|
||||||
|
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
|
||||||
import type {Fiber} from './ReactFiber';
|
import type {Fiber} from './ReactFiber';
|
||||||
import type {FiberRoot} from './ReactFiberRoot';
|
import type {FiberRoot} from './ReactFiberRoot';
|
||||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||||
|
@ -73,7 +74,6 @@ import invariant from 'shared/invariant';
|
||||||
import shallowEqual from 'shared/shallowEqual';
|
import shallowEqual from 'shared/shallowEqual';
|
||||||
import getComponentName from 'shared/getComponentName';
|
import getComponentName from 'shared/getComponentName';
|
||||||
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
|
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
|
||||||
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
|
|
||||||
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
|
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
|
||||||
import {
|
import {
|
||||||
getCurrentFiberOwnerNameInDevOrNull,
|
getCurrentFiberOwnerNameInDevOrNull,
|
||||||
|
@ -164,11 +164,7 @@ import {
|
||||||
resumeMountClassInstance,
|
resumeMountClassInstance,
|
||||||
updateClassInstance,
|
updateClassInstance,
|
||||||
} from './ReactFiberClassComponent';
|
} from './ReactFiberClassComponent';
|
||||||
import {
|
import {resolveDefaultProps} from './ReactFiberLazyComponent';
|
||||||
readLazyComponentType,
|
|
||||||
resolveDefaultProps,
|
|
||||||
} from './ReactFiberLazyComponent';
|
|
||||||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
|
|
||||||
import {
|
import {
|
||||||
resolveLazyComponentTag,
|
resolveLazyComponentTag,
|
||||||
createFiberFromTypeAndProps,
|
createFiberFromTypeAndProps,
|
||||||
|
@ -184,7 +180,6 @@ import {
|
||||||
renderDidSuspendDelayIfPossible,
|
renderDidSuspendDelayIfPossible,
|
||||||
markUnprocessedUpdateTime,
|
markUnprocessedUpdateTime,
|
||||||
} from './ReactFiberWorkLoop';
|
} from './ReactFiberWorkLoop';
|
||||||
import {Resolved} from 'shared/ReactLazyStatusTags';
|
|
||||||
|
|
||||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||||
|
|
||||||
|
@ -492,7 +487,14 @@ function updateSimpleMemoComponent(
|
||||||
// We warn when you define propTypes on lazy()
|
// We warn when you define propTypes on lazy()
|
||||||
// so let's just skip over it to find memo() outer wrapper.
|
// so let's just skip over it to find memo() outer wrapper.
|
||||||
// Inner props for memo are validated later.
|
// Inner props for memo are validated later.
|
||||||
outerMemoType = refineResolvedLazyComponent(outerMemoType);
|
const lazyComponent: LazyComponentType<any, any> = outerMemoType;
|
||||||
|
let payload = lazyComponent._payload;
|
||||||
|
let init = lazyComponent._init;
|
||||||
|
try {
|
||||||
|
outerMemoType = init(payload);
|
||||||
|
} catch (x) {
|
||||||
|
outerMemoType = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
|
const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
|
||||||
if (outerPropTypes) {
|
if (outerPropTypes) {
|
||||||
|
@ -703,10 +705,10 @@ function updateFunctionComponent(
|
||||||
return workInProgress.child;
|
return workInProgress.child;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBlock<Props, Payload, Data>(
|
function updateBlock<Props, Data>(
|
||||||
current: Fiber | null,
|
current: Fiber | null,
|
||||||
workInProgress: Fiber,
|
workInProgress: Fiber,
|
||||||
block: BlockComponent<Props, Payload, Data>,
|
block: BlockComponent<Props, Data>,
|
||||||
nextProps: any,
|
nextProps: any,
|
||||||
renderExpirationTime: ExpirationTime,
|
renderExpirationTime: ExpirationTime,
|
||||||
) {
|
) {
|
||||||
|
@ -714,12 +716,7 @@ function updateBlock<Props, Payload, Data>(
|
||||||
// hasn't yet mounted. This happens after the first render suspends.
|
// hasn't yet mounted. This happens after the first render suspends.
|
||||||
// We'll need to figure out if this is fine or can cause issues.
|
// We'll need to figure out if this is fine or can cause issues.
|
||||||
|
|
||||||
initializeBlockComponentType(block);
|
const render = block._render;
|
||||||
if (block._status !== Resolved) {
|
|
||||||
throw block._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const render = block._fn;
|
|
||||||
const data = block._data;
|
const data = block._data;
|
||||||
|
|
||||||
// The rest is a fork of updateFunctionComponent
|
// The rest is a fork of updateFunctionComponent
|
||||||
|
@ -1142,7 +1139,10 @@ function mountLazyComponent(
|
||||||
// We can't start a User Timing measurement with correct label yet.
|
// We can't start a User Timing measurement with correct label yet.
|
||||||
// Cancel and resume right after we know the tag.
|
// Cancel and resume right after we know the tag.
|
||||||
cancelWorkTimer(workInProgress);
|
cancelWorkTimer(workInProgress);
|
||||||
let Component = readLazyComponentType(elementType);
|
let lazyComponent: LazyComponentType<any, any> = elementType;
|
||||||
|
let payload = lazyComponent._payload;
|
||||||
|
let init = lazyComponent._init;
|
||||||
|
let Component = init(payload);
|
||||||
// Store the unwrapped component in the type.
|
// Store the unwrapped component in the type.
|
||||||
workInProgress.type = Component;
|
workInProgress.type = Component;
|
||||||
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
|
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {LazyComponent} from 'react/src/ReactLazy';
|
|
||||||
|
|
||||||
import {Resolved} from 'shared/ReactLazyStatusTags';
|
|
||||||
import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
|
|
||||||
|
|
||||||
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||||
if (Component && Component.defaultProps) {
|
if (Component && Component.defaultProps) {
|
||||||
// Resolve default props. Taken from ReactElement
|
// Resolve default props. Taken from ReactElement
|
||||||
|
@ -26,11 +21,3 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||||
}
|
}
|
||||||
return baseProps;
|
return baseProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
|
|
||||||
initializeLazyComponentType(lazyComponent);
|
|
||||||
if (lazyComponent._status !== Resolved) {
|
|
||||||
throw lazyComponent._result;
|
|
||||||
}
|
|
||||||
return lazyComponent._result;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,11 +14,13 @@ let useState;
|
||||||
let Suspense;
|
let Suspense;
|
||||||
let block;
|
let block;
|
||||||
let readString;
|
let readString;
|
||||||
|
let Scheduler;
|
||||||
|
|
||||||
describe('ReactBlocks', () => {
|
describe('ReactBlocks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
||||||
|
Scheduler = require('scheduler');
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactNoop = require('react-noop-renderer');
|
ReactNoop = require('react-noop-renderer');
|
||||||
|
|
||||||
|
@ -47,6 +49,43 @@ describe('ReactBlocks', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.experimental('prints the name of the render function in warnings', () => {
|
||||||
|
function Query(firstName) {
|
||||||
|
return {
|
||||||
|
name: firstName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function User(props, data) {
|
||||||
|
let array = [<span>{data.name}</span>];
|
||||||
|
return <div>{array}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App({Component}) {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={'Loading...'}>
|
||||||
|
<Component name="Name" />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let loadUser = block(Query, User);
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
ReactNoop.act(() => {
|
||||||
|
ReactNoop.render(<App Component={loadUser()} />);
|
||||||
|
});
|
||||||
|
}).toErrorDev(
|
||||||
|
'Warning: Each child in a list should have a unique ' +
|
||||||
|
'"key" prop.\n\nCheck the render method of `User`. See ' +
|
||||||
|
'https://fb.me/react-warning-keys for more information.\n' +
|
||||||
|
' in span (at **)\n' +
|
||||||
|
' in User (at **)\n' +
|
||||||
|
' in Suspense (at **)\n' +
|
||||||
|
' in App (at **)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it.experimental('renders a component with a suspending query', async () => {
|
it.experimental('renders a component with a suspending query', async () => {
|
||||||
function Query(id) {
|
function Query(id) {
|
||||||
return {
|
return {
|
||||||
|
@ -86,7 +125,9 @@ describe('ReactBlocks', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
|
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.experimental('supports a lazy wrapper around a chunk', async () => {
|
it.experimental(
|
||||||
|
'does not support a lazy wrapper around a chunk',
|
||||||
|
async () => {
|
||||||
function Query(id) {
|
function Query(id) {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -131,19 +172,17 @@ describe('ReactBlocks', () => {
|
||||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||||
|
|
||||||
// Resolve the component.
|
// Resolve the component.
|
||||||
await ReactNoop.act(async () => {
|
|
||||||
await resolveLazy();
|
await resolveLazy();
|
||||||
});
|
|
||||||
|
|
||||||
// We're still waiting on the data.
|
expect(Scheduler).toFlushAndThrow(
|
||||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
|
||||||
|
'Lazy element type must resolve to a class or function.' +
|
||||||
await ReactNoop.act(async () => {
|
(__DEV__
|
||||||
jest.advanceTimersByTime(1000);
|
? ' Did you wrap a component in React.lazy() more than once?'
|
||||||
});
|
: ''),
|
||||||
|
);
|
||||||
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
it.experimental(
|
it.experimental(
|
||||||
'can receive updated data for the same component',
|
'can receive updated data for the same component',
|
||||||
|
|
|
@ -879,7 +879,13 @@ describe('ReactLazy', () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
// Getting the name for the warning cause the loading to start early.
|
||||||
|
expect(Scheduler).toHaveYielded(['Started loading']);
|
||||||
|
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||||
|
} else {
|
||||||
expect(Scheduler).toFlushAndYield(['Started loading', 'Loading...']);
|
expect(Scheduler).toFlushAndYield(['Started loading', 'Loading...']);
|
||||||
|
}
|
||||||
expect(root).not.toMatchRenderedOutput(<div>AB</div>);
|
expect(root).not.toMatchRenderedOutput(<div>AB</div>);
|
||||||
|
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {LazyComponent} from './ReactLazy';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
REACT_LAZY_TYPE,
|
||||||
REACT_BLOCK_TYPE,
|
REACT_BLOCK_TYPE,
|
||||||
REACT_MEMO_TYPE,
|
REACT_MEMO_TYPE,
|
||||||
REACT_FORWARD_REF_TYPE,
|
REACT_FORWARD_REF_TYPE,
|
||||||
|
@ -19,55 +22,33 @@ type BlockRenderFunction<Props, Data> = (
|
||||||
data: Data,
|
data: Data,
|
||||||
) => React$Node;
|
) => React$Node;
|
||||||
|
|
||||||
type Thenable<T, R> = {
|
type Payload<Props, Args: Iterable<any>, Data> = {
|
||||||
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
query: BlockQueryFunction<Args, Data>,
|
||||||
|
args: Args,
|
||||||
|
render: BlockRenderFunction<Props, Data>,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Initializer<Props, Payload, Data> = (
|
export type BlockComponent<Props, Data> = {
|
||||||
payload: Payload,
|
|
||||||
) =>
|
|
||||||
| [Data, BlockRenderFunction<Props, Data>]
|
|
||||||
| Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>;
|
|
||||||
|
|
||||||
export type UninitializedBlockComponent<Props, Payload, Data> = {
|
|
||||||
$$typeof: Symbol | number,
|
$$typeof: Symbol | number,
|
||||||
_status: -1,
|
|
||||||
_data: Payload,
|
|
||||||
_fn: Initializer<Props, Payload, Data>,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PendingBlockComponent<Props, Data> = {
|
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 0,
|
|
||||||
_data: Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>,
|
|
||||||
_fn: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ResolvedBlockComponent<Props, Data> = {
|
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 1,
|
|
||||||
_data: Data,
|
_data: Data,
|
||||||
_fn: BlockRenderFunction<Props, Data>,
|
_render: BlockRenderFunction<Props, Data>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RejectedBlockComponent = {
|
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 2,
|
|
||||||
_data: mixed,
|
|
||||||
_fn: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BlockComponent<Props, Payload, Data> =
|
|
||||||
| UninitializedBlockComponent<Props, Payload, Data>
|
|
||||||
| PendingBlockComponent<Props, Data>
|
|
||||||
| ResolvedBlockComponent<Props, Data>
|
|
||||||
| RejectedBlockComponent;
|
|
||||||
|
|
||||||
opaque type Block<Props>: React$AbstractComponent<
|
opaque type Block<Props>: React$AbstractComponent<
|
||||||
Props,
|
Props,
|
||||||
null,
|
null,
|
||||||
> = React$AbstractComponent<Props, null>;
|
> = React$AbstractComponent<Props, null>;
|
||||||
|
|
||||||
|
function lazyInitializer<Props, Args: Iterable<any>, Data>(
|
||||||
|
payload: Payload<Props, Args, Data>,
|
||||||
|
): BlockComponent<Props, Data> {
|
||||||
|
return {
|
||||||
|
$$typeof: REACT_BLOCK_TYPE,
|
||||||
|
_data: payload.query.apply(null, payload.args),
|
||||||
|
_render: payload.render,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function block<Args: Iterable<any>, Props, Data>(
|
export function block<Args: Iterable<any>, Props, Data>(
|
||||||
query: BlockQueryFunction<Args, Data>,
|
query: BlockQueryFunction<Args, Data>,
|
||||||
render: BlockRenderFunction<Props, Data>,
|
render: BlockRenderFunction<Props, Data>,
|
||||||
|
@ -115,19 +96,26 @@ export function block<Args: Iterable<any>, Props, Data>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function initializer(args) {
|
|
||||||
let data = query.apply(null, args);
|
|
||||||
return [data, render];
|
|
||||||
}
|
|
||||||
return function(): Block<Props> {
|
return function(): Block<Props> {
|
||||||
let args: Args = arguments;
|
let args: Args = arguments;
|
||||||
let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
|
|
||||||
$$typeof: REACT_BLOCK_TYPE,
|
let payload: Payload<Props, Args, Data> = {
|
||||||
_status: -1,
|
query: query,
|
||||||
_data: args,
|
args: args,
|
||||||
_fn: initializer,
|
render: render,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lazyType: LazyComponent<
|
||||||
|
BlockComponent<Props, Data>,
|
||||||
|
Payload<Props, Args, Data>,
|
||||||
|
> = {
|
||||||
|
$$typeof: REACT_LAZY_TYPE,
|
||||||
|
_payload: payload,
|
||||||
|
_init: lazyInitializer,
|
||||||
|
};
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
return blockComponent;
|
return lazyType;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,46 +13,105 @@ type Thenable<T, R> = {
|
||||||
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UninitializedLazyComponent<T> = {
|
const Uninitialized = -1;
|
||||||
$$typeof: Symbol | number,
|
const Pending = 0;
|
||||||
|
const Resolved = 1;
|
||||||
|
const Rejected = 2;
|
||||||
|
|
||||||
|
type UninitializedPayload<T> = {
|
||||||
_status: -1,
|
_status: -1,
|
||||||
_result: () => Thenable<{default: T, ...} | T, mixed>,
|
_result: () => Thenable<{default: T, ...}, mixed>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PendingLazyComponent<T> = {
|
type PendingPayload<T> = {
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 0,
|
_status: 0,
|
||||||
_result: Thenable<{default: T, ...} | T, mixed>,
|
_result: Thenable<{default: T, ...}, mixed>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResolvedLazyComponent<T> = {
|
type ResolvedPayload<T> = {
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 1,
|
_status: 1,
|
||||||
_result: T,
|
_result: T,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RejectedLazyComponent = {
|
type RejectedPayload = {
|
||||||
$$typeof: Symbol | number,
|
|
||||||
_status: 2,
|
_status: 2,
|
||||||
_result: mixed,
|
_result: mixed,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LazyComponent<T> =
|
type Payload<T> =
|
||||||
| UninitializedLazyComponent<T>
|
| UninitializedPayload<T>
|
||||||
| PendingLazyComponent<T>
|
| PendingPayload<T>
|
||||||
| ResolvedLazyComponent<T>
|
| ResolvedPayload<T>
|
||||||
| RejectedLazyComponent;
|
| RejectedPayload;
|
||||||
|
|
||||||
|
export type LazyComponent<T, P> = {
|
||||||
|
$$typeof: Symbol | number,
|
||||||
|
_payload: P,
|
||||||
|
_init: (payload: P) => T,
|
||||||
|
};
|
||||||
|
|
||||||
|
function lazyInitializer<T>(payload: Payload<T>): T {
|
||||||
|
if (payload._status === Uninitialized) {
|
||||||
|
const ctor = payload._result;
|
||||||
|
const thenable = ctor();
|
||||||
|
// Transition to the next state.
|
||||||
|
const pending: PendingPayload<any> = (payload: any);
|
||||||
|
pending._status = Pending;
|
||||||
|
pending._result = thenable;
|
||||||
|
thenable.then(
|
||||||
|
moduleObject => {
|
||||||
|
if (payload._status === Pending) {
|
||||||
|
const defaultExport = moduleObject.default;
|
||||||
|
if (__DEV__) {
|
||||||
|
if (defaultExport === undefined) {
|
||||||
|
console.error(
|
||||||
|
'lazy: Expected the result of a dynamic import() call. ' +
|
||||||
|
'Instead received: %s\n\nYour code should look like: \n ' +
|
||||||
|
// Break up imports to avoid accidentally parsing them as dependencies.
|
||||||
|
'const MyComponent = lazy(() => imp' +
|
||||||
|
"ort('./MyComponent'))",
|
||||||
|
moduleObject,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Transition to the next state.
|
||||||
|
const resolved: ResolvedPayload<any> = (payload: any);
|
||||||
|
resolved._status = Resolved;
|
||||||
|
resolved._result = defaultExport;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (payload._status === Pending) {
|
||||||
|
// Transition to the next state.
|
||||||
|
const rejected: RejectedPayload = (payload: any);
|
||||||
|
rejected._status = Rejected;
|
||||||
|
rejected._result = error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (payload._status === Resolved) {
|
||||||
|
return payload._result;
|
||||||
|
} else {
|
||||||
|
throw payload._result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function lazy<T>(
|
export function lazy<T>(
|
||||||
ctor: () => Thenable<{default: T, ...} | T, mixed>,
|
ctor: () => Thenable<{default: T, ...}, mixed>,
|
||||||
): LazyComponent<T> {
|
): LazyComponent<T, Payload<T>> {
|
||||||
let lazyType: LazyComponent<T> = {
|
let payload: Payload<T> = {
|
||||||
$$typeof: REACT_LAZY_TYPE,
|
// We use these fields to store the result.
|
||||||
// React uses these fields to store the result.
|
|
||||||
_status: -1,
|
_status: -1,
|
||||||
_result: ctor,
|
_result: ctor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lazyType: LazyComponent<T, Payload<T>> = {
|
||||||
|
$$typeof: REACT_LAZY_TYPE,
|
||||||
|
_payload: payload,
|
||||||
|
_init: lazyInitializer,
|
||||||
|
};
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// In production, this would just set it on the object.
|
// In production, this would just set it on the object.
|
||||||
let defaultProps;
|
let defaultProps;
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {
|
|
||||||
PendingLazyComponent,
|
|
||||||
ResolvedLazyComponent,
|
|
||||||
RejectedLazyComponent,
|
|
||||||
LazyComponent,
|
|
||||||
} from 'react/src/ReactLazy';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
PendingBlockComponent,
|
|
||||||
ResolvedBlockComponent,
|
|
||||||
RejectedBlockComponent,
|
|
||||||
BlockComponent,
|
|
||||||
} from 'react/src/ReactBlock';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Uninitialized,
|
|
||||||
Pending,
|
|
||||||
Resolved,
|
|
||||||
Rejected,
|
|
||||||
} from './ReactLazyStatusTags';
|
|
||||||
|
|
||||||
export function refineResolvedLazyComponent<T>(
|
|
||||||
lazyComponent: LazyComponent<T>,
|
|
||||||
): T | null {
|
|
||||||
return lazyComponent._status === Resolved ? lazyComponent._result : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeLazyComponentType(
|
|
||||||
lazyComponent: LazyComponent<any>,
|
|
||||||
): void {
|
|
||||||
if (lazyComponent._status === Uninitialized) {
|
|
||||||
const ctor = lazyComponent._result;
|
|
||||||
const thenable = ctor();
|
|
||||||
// Transition to the next state.
|
|
||||||
const pending: PendingLazyComponent<any> = (lazyComponent: any);
|
|
||||||
pending._status = Pending;
|
|
||||||
pending._result = thenable;
|
|
||||||
thenable.then(
|
|
||||||
moduleObject => {
|
|
||||||
if (lazyComponent._status === Pending) {
|
|
||||||
const defaultExport = moduleObject.default;
|
|
||||||
if (__DEV__) {
|
|
||||||
if (defaultExport === undefined) {
|
|
||||||
console.error(
|
|
||||||
'lazy: Expected the result of a dynamic import() call. ' +
|
|
||||||
'Instead received: %s\n\nYour code should look like: \n ' +
|
|
||||||
// Break up imports to avoid accidentally parsing them as dependencies.
|
|
||||||
'const MyComponent = lazy(() => imp' +
|
|
||||||
"ort('./MyComponent'))",
|
|
||||||
moduleObject,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Transition to the next state.
|
|
||||||
const resolved: ResolvedLazyComponent<any> = (lazyComponent: any);
|
|
||||||
resolved._status = Resolved;
|
|
||||||
resolved._result = defaultExport;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if (lazyComponent._status === Pending) {
|
|
||||||
// Transition to the next state.
|
|
||||||
const rejected: RejectedLazyComponent = (lazyComponent: any);
|
|
||||||
rejected._status = Rejected;
|
|
||||||
rejected._result = error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeBlockComponentType<Props, Payload, Data>(
|
|
||||||
blockComponent: BlockComponent<Props, Payload, Data>,
|
|
||||||
): void {
|
|
||||||
if (blockComponent._status === Uninitialized) {
|
|
||||||
const thenableOrTuple = blockComponent._fn(blockComponent._data);
|
|
||||||
if (typeof thenableOrTuple.then !== 'function') {
|
|
||||||
let tuple: [any, any] = (thenableOrTuple: any);
|
|
||||||
const resolved: ResolvedBlockComponent<
|
|
||||||
Props,
|
|
||||||
Data,
|
|
||||||
> = (blockComponent: any);
|
|
||||||
resolved._status = Resolved;
|
|
||||||
resolved._data = tuple[0];
|
|
||||||
resolved._fn = tuple[1];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const thenable = (thenableOrTuple: any);
|
|
||||||
// Transition to the next state.
|
|
||||||
const pending: PendingBlockComponent<Props, Data> = (blockComponent: any);
|
|
||||||
pending._status = Pending;
|
|
||||||
pending._data = thenable;
|
|
||||||
pending._fn = null;
|
|
||||||
thenable.then(
|
|
||||||
(tuple: [any, any]) => {
|
|
||||||
if (blockComponent._status === Pending) {
|
|
||||||
// Transition to the next state.
|
|
||||||
const resolved: ResolvedBlockComponent<
|
|
||||||
Props,
|
|
||||||
Data,
|
|
||||||
> = (blockComponent: any);
|
|
||||||
resolved._status = Resolved;
|
|
||||||
resolved._data = tuple[0];
|
|
||||||
resolved._fn = tuple[1];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if (blockComponent._status === Pending) {
|
|
||||||
// Transition to the next state.
|
|
||||||
const rejected: RejectedBlockComponent = (blockComponent: any);
|
|
||||||
rejected._status = Rejected;
|
|
||||||
rejected._data = error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: Move this to "react" once we can import from externals.
|
|
||||||
export const Uninitialized = -1;
|
|
||||||
export const Pending = 0;
|
|
||||||
export const Resolved = 1;
|
|
||||||
export const Rejected = 2;
|
|
|
@ -23,7 +23,6 @@ import {
|
||||||
REACT_LAZY_TYPE,
|
REACT_LAZY_TYPE,
|
||||||
REACT_BLOCK_TYPE,
|
REACT_BLOCK_TYPE,
|
||||||
} from 'shared/ReactSymbols';
|
} from 'shared/ReactSymbols';
|
||||||
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
|
|
||||||
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
||||||
|
|
||||||
function getWrappedName(
|
function getWrappedName(
|
||||||
|
@ -88,14 +87,16 @@ function getComponentName(type: mixed): string | null {
|
||||||
case REACT_MEMO_TYPE:
|
case REACT_MEMO_TYPE:
|
||||||
return getComponentName(type.type);
|
return getComponentName(type.type);
|
||||||
case REACT_BLOCK_TYPE:
|
case REACT_BLOCK_TYPE:
|
||||||
return getComponentName(type.render);
|
return getComponentName(type._render);
|
||||||
case REACT_LAZY_TYPE: {
|
case REACT_LAZY_TYPE: {
|
||||||
const thenable: LazyComponent<mixed> = (type: any);
|
const lazyComponent: LazyComponent<any, any> = (type: any);
|
||||||
const resolvedThenable = refineResolvedLazyComponent(thenable);
|
let payload = lazyComponent._payload;
|
||||||
if (resolvedThenable) {
|
let init = lazyComponent._init;
|
||||||
return getComponentName(resolvedThenable);
|
try {
|
||||||
|
return getComponentName(init(payload));
|
||||||
|
} catch (x) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue