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 />);
|
||||
}).toThrow('ReactDOMServer does not yet support lazy-loaded components.');
|
||||
}).toThrow('ReactDOMServer does not yet support Suspense.');
|
||||
});
|
||||
|
||||
it('throws when suspending on the server', () => {
|
||||
|
|
|
@ -17,8 +17,6 @@ import invariant from 'shared/invariant';
|
|||
import getComponentName from 'shared/getComponentName';
|
||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {initializeLazyComponentType} from 'shared/ReactLazyComponent';
|
||||
import {Resolved, Rejected, Pending} from 'shared/ReactLazyStatusTags';
|
||||
import {
|
||||
warnAboutDeprecatedLifecycles,
|
||||
disableLegacyContext,
|
||||
|
@ -1233,42 +1231,33 @@ class ReactDOMServerRenderer {
|
|||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_LAZY_TYPE: {
|
||||
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
|
||||
// suspense server-side renderer is enabled so synchronously
|
||||
// resolved constructors are supported.
|
||||
initializeLazyComponentType(lazyComponent);
|
||||
switch (lazyComponent._status) {
|
||||
case Resolved: {
|
||||
const nextChildren = [
|
||||
React.createElement(
|
||||
lazyComponent._result,
|
||||
Object.assign({ref: element.ref}, element.props),
|
||||
),
|
||||
];
|
||||
const frame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
case Rejected:
|
||||
throw lazyComponent._result;
|
||||
case Pending:
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOMServer does not yet support lazy-loaded components.',
|
||||
);
|
||||
let payload = lazyComponent._payload;
|
||||
let init = lazyComponent._init;
|
||||
let result = init(payload);
|
||||
const nextChildren = [
|
||||
React.createElement(
|
||||
result,
|
||||
Object.assign({ref: element.ref}, element.props),
|
||||
),
|
||||
];
|
||||
const frame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_SCOPE_TYPE: {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {ReactPortal} from 'shared/ReactTypes';
|
||||
import type {BlockComponent} from 'react/src/ReactBlock';
|
||||
import type {LazyComponent} from 'react/src/ReactLazy';
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
|
||||
|
@ -20,6 +21,7 @@ import {
|
|||
REACT_ELEMENT_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_PORTAL_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_BLOCK_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {
|
||||
|
@ -48,7 +50,6 @@ import {
|
|||
} from './ReactCurrentFiber';
|
||||
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
|
||||
import {StrictMode} from './ReactTypeOfMode';
|
||||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
|
||||
|
||||
let didWarnAboutMaps;
|
||||
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
|
||||
// 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
|
||||
|
@ -419,22 +436,22 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
existing._debugOwner = element._owner;
|
||||
}
|
||||
return existing;
|
||||
} else if (
|
||||
enableBlocksAPI &&
|
||||
current.tag === Block &&
|
||||
element.type.$$typeof === REACT_BLOCK_TYPE
|
||||
) {
|
||||
} else if (enableBlocksAPI && current.tag === Block) {
|
||||
// The new Block might not be initialized yet. We need to initialize
|
||||
// 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 (
|
||||
(element.type: BlockComponent<any, any, any>)._fn ===
|
||||
(current.type: BlockComponent<any, any, any>)._fn
|
||||
type.$$typeof === REACT_BLOCK_TYPE &&
|
||||
((type: any): BlockComponent<any, any>)._render ===
|
||||
(current.type: BlockComponent<any, any>)._render
|
||||
) {
|
||||
// Same as above but also update the .type field.
|
||||
const existing = useFiber(current, element.props);
|
||||
existing.return = returnFiber;
|
||||
existing.type = element.type;
|
||||
existing.type = type;
|
||||
if (__DEV__) {
|
||||
existing._debugSource = element._source;
|
||||
existing._debugOwner = element._owner;
|
||||
|
@ -1188,17 +1205,20 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
}
|
||||
case Block:
|
||||
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
|
||||
// it in case initializing it turns out it would match.
|
||||
initializeBlockComponentType(element.type);
|
||||
if (
|
||||
(element.type: BlockComponent<any, any, any>)._fn ===
|
||||
(child.type: BlockComponent<any, any, any>)._fn
|
||||
((type: any): BlockComponent<any, any>)._render ===
|
||||
(child.type: BlockComponent<any, any>)._render
|
||||
) {
|
||||
deleteRemainingChildren(returnFiber, child.sibling);
|
||||
const existing = useFiber(child, element.props);
|
||||
existing.type = element.type;
|
||||
existing.type = type;
|
||||
existing.return = returnFiber;
|
||||
if (__DEV__) {
|
||||
existing._debugSource = element._source;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
|
||||
import type {BlockComponent} from 'react/src/ReactBlock';
|
||||
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
|
@ -73,7 +74,6 @@ import invariant from 'shared/invariant';
|
|||
import shallowEqual from 'shared/shallowEqual';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
|
||||
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
|
||||
import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols';
|
||||
import {
|
||||
getCurrentFiberOwnerNameInDevOrNull,
|
||||
|
@ -164,11 +164,7 @@ import {
|
|||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
} from './ReactFiberClassComponent';
|
||||
import {
|
||||
readLazyComponentType,
|
||||
resolveDefaultProps,
|
||||
} from './ReactFiberLazyComponent';
|
||||
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';
|
||||
import {resolveDefaultProps} from './ReactFiberLazyComponent';
|
||||
import {
|
||||
resolveLazyComponentTag,
|
||||
createFiberFromTypeAndProps,
|
||||
|
@ -184,7 +180,6 @@ import {
|
|||
renderDidSuspendDelayIfPossible,
|
||||
markUnprocessedUpdateTime,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {Resolved} from 'shared/ReactLazyStatusTags';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
|
||||
|
@ -492,7 +487,14 @@ function updateSimpleMemoComponent(
|
|||
// We warn when you define propTypes on lazy()
|
||||
// so let's just skip over it to find memo() outer wrapper.
|
||||
// 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;
|
||||
if (outerPropTypes) {
|
||||
|
@ -703,10 +705,10 @@ function updateFunctionComponent(
|
|||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateBlock<Props, Payload, Data>(
|
||||
function updateBlock<Props, Data>(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
block: BlockComponent<Props, Payload, Data>,
|
||||
block: BlockComponent<Props, Data>,
|
||||
nextProps: any,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
|
@ -714,12 +716,7 @@ function updateBlock<Props, Payload, Data>(
|
|||
// 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.
|
||||
|
||||
initializeBlockComponentType(block);
|
||||
if (block._status !== Resolved) {
|
||||
throw block._data;
|
||||
}
|
||||
|
||||
const render = block._fn;
|
||||
const render = block._render;
|
||||
const data = block._data;
|
||||
|
||||
// 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.
|
||||
// Cancel and resume right after we know the tag.
|
||||
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.
|
||||
workInProgress.type = Component;
|
||||
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component));
|
||||
|
|
|
@ -7,11 +7,6 @@
|
|||
* @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 {
|
||||
if (Component && Component.defaultProps) {
|
||||
// Resolve default props. Taken from ReactElement
|
||||
|
@ -26,11 +21,3 @@ export function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
|||
}
|
||||
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 block;
|
||||
let readString;
|
||||
let Scheduler;
|
||||
|
||||
describe('ReactBlocks', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
Scheduler = require('scheduler');
|
||||
React = require('react');
|
||||
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 () => {
|
||||
function Query(id) {
|
||||
return {
|
||||
|
@ -86,64 +125,64 @@ describe('ReactBlocks', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
|
||||
});
|
||||
|
||||
it.experimental('supports a lazy wrapper around a chunk', async () => {
|
||||
function Query(id) {
|
||||
return {
|
||||
id: id,
|
||||
name: readString('Sebastian'),
|
||||
};
|
||||
}
|
||||
it.experimental(
|
||||
'does not support a lazy wrapper around a chunk',
|
||||
async () => {
|
||||
function Query(id) {
|
||||
return {
|
||||
id: id,
|
||||
name: readString('Sebastian'),
|
||||
};
|
||||
}
|
||||
|
||||
function Render(props, data) {
|
||||
return (
|
||||
<span>
|
||||
{props.title}: {data.name}
|
||||
</span>
|
||||
function Render(props, data) {
|
||||
return (
|
||||
<span>
|
||||
{props.title}: {data.name}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, Render);
|
||||
|
||||
function App({User}) {
|
||||
return (
|
||||
<Suspense fallback={'Loading...'}>
|
||||
<User title="Name" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
let resolveLazy;
|
||||
let LazyUser = React.lazy(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveLazy = function() {
|
||||
resolve({
|
||||
default: loadUser(123),
|
||||
});
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let loadUser = block(Query, Render);
|
||||
await ReactNoop.act(async () => {
|
||||
ReactNoop.render(<App User={LazyUser} />);
|
||||
});
|
||||
|
||||
function App({User}) {
|
||||
return (
|
||||
<Suspense fallback={'Loading...'}>
|
||||
<User title="Name" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
let resolveLazy;
|
||||
let LazyUser = React.lazy(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveLazy = function() {
|
||||
resolve({
|
||||
default: loadUser(123),
|
||||
});
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
await ReactNoop.act(async () => {
|
||||
ReactNoop.render(<App User={LazyUser} />);
|
||||
});
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Resolve the component.
|
||||
await ReactNoop.act(async () => {
|
||||
// Resolve the component.
|
||||
await resolveLazy();
|
||||
});
|
||||
|
||||
// We're still waiting on the data.
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await ReactNoop.act(async () => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
|
||||
});
|
||||
expect(Scheduler).toFlushAndThrow(
|
||||
'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
|
||||
'Lazy element type must resolve to a class or function.' +
|
||||
(__DEV__
|
||||
? ' Did you wrap a component in React.lazy() more than once?'
|
||||
: ''),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.experimental(
|
||||
'can receive updated data for the same component',
|
||||
|
|
|
@ -879,7 +879,13 @@ describe('ReactLazy', () => {
|
|||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Started loading', 'Loading...']);
|
||||
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(root).not.toMatchRenderedOutput(<div>AB</div>);
|
||||
|
||||
await Promise.resolve();
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import type {LazyComponent} from './ReactLazy';
|
||||
|
||||
import {
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_BLOCK_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
|
@ -19,55 +22,33 @@ type BlockRenderFunction<Props, Data> = (
|
|||
data: Data,
|
||||
) => React$Node;
|
||||
|
||||
type Thenable<T, R> = {
|
||||
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
||||
type Payload<Props, Args: Iterable<any>, Data> = {
|
||||
query: BlockQueryFunction<Args, Data>,
|
||||
args: Args,
|
||||
render: BlockRenderFunction<Props, Data>,
|
||||
};
|
||||
|
||||
type Initializer<Props, Payload, Data> = (
|
||||
payload: Payload,
|
||||
) =>
|
||||
| [Data, BlockRenderFunction<Props, Data>]
|
||||
| Thenable<[Data, BlockRenderFunction<Props, Data>], mixed>;
|
||||
|
||||
export type UninitializedBlockComponent<Props, Payload, Data> = {
|
||||
export type BlockComponent<Props, Data> = {
|
||||
$$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,
|
||||
_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<
|
||||
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>(
|
||||
query: BlockQueryFunction<Args, 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> {
|
||||
let args: Args = arguments;
|
||||
let blockComponent: UninitializedBlockComponent<Props, Args, Data> = {
|
||||
$$typeof: REACT_BLOCK_TYPE,
|
||||
_status: -1,
|
||||
_data: args,
|
||||
_fn: initializer,
|
||||
|
||||
let payload: Payload<Props, Args, Data> = {
|
||||
query: query,
|
||||
args: args,
|
||||
render: render,
|
||||
};
|
||||
|
||||
let lazyType: LazyComponent<
|
||||
BlockComponent<Props, Data>,
|
||||
Payload<Props, Args, Data>,
|
||||
> = {
|
||||
$$typeof: REACT_LAZY_TYPE,
|
||||
_payload: payload,
|
||||
_init: lazyInitializer,
|
||||
};
|
||||
|
||||
// $FlowFixMe
|
||||
return blockComponent;
|
||||
return lazyType;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,46 +13,105 @@ type Thenable<T, R> = {
|
|||
then(resolve: (T) => mixed, reject: (mixed) => mixed): R,
|
||||
};
|
||||
|
||||
export type UninitializedLazyComponent<T> = {
|
||||
$$typeof: Symbol | number,
|
||||
const Uninitialized = -1;
|
||||
const Pending = 0;
|
||||
const Resolved = 1;
|
||||
const Rejected = 2;
|
||||
|
||||
type UninitializedPayload<T> = {
|
||||
_status: -1,
|
||||
_result: () => Thenable<{default: T, ...} | T, mixed>,
|
||||
_result: () => Thenable<{default: T, ...}, mixed>,
|
||||
};
|
||||
|
||||
export type PendingLazyComponent<T> = {
|
||||
$$typeof: Symbol | number,
|
||||
type PendingPayload<T> = {
|
||||
_status: 0,
|
||||
_result: Thenable<{default: T, ...} | T, mixed>,
|
||||
_result: Thenable<{default: T, ...}, mixed>,
|
||||
};
|
||||
|
||||
export type ResolvedLazyComponent<T> = {
|
||||
$$typeof: Symbol | number,
|
||||
type ResolvedPayload<T> = {
|
||||
_status: 1,
|
||||
_result: T,
|
||||
};
|
||||
|
||||
export type RejectedLazyComponent = {
|
||||
$$typeof: Symbol | number,
|
||||
type RejectedPayload = {
|
||||
_status: 2,
|
||||
_result: mixed,
|
||||
};
|
||||
|
||||
export type LazyComponent<T> =
|
||||
| UninitializedLazyComponent<T>
|
||||
| PendingLazyComponent<T>
|
||||
| ResolvedLazyComponent<T>
|
||||
| RejectedLazyComponent;
|
||||
type Payload<T> =
|
||||
| UninitializedPayload<T>
|
||||
| PendingPayload<T>
|
||||
| ResolvedPayload<T>
|
||||
| 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>(
|
||||
ctor: () => Thenable<{default: T, ...} | T, mixed>,
|
||||
): LazyComponent<T> {
|
||||
let lazyType: LazyComponent<T> = {
|
||||
$$typeof: REACT_LAZY_TYPE,
|
||||
// React uses these fields to store the result.
|
||||
ctor: () => Thenable<{default: T, ...}, mixed>,
|
||||
): LazyComponent<T, Payload<T>> {
|
||||
let payload: Payload<T> = {
|
||||
// We use these fields to store the result.
|
||||
_status: -1,
|
||||
_result: ctor,
|
||||
};
|
||||
|
||||
let lazyType: LazyComponent<T, Payload<T>> = {
|
||||
$$typeof: REACT_LAZY_TYPE,
|
||||
_payload: payload,
|
||||
_init: lazyInitializer,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// In production, this would just set it on the object.
|
||||
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_BLOCK_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent';
|
||||
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
|
||||
|
||||
function getWrappedName(
|
||||
|
@ -88,14 +87,16 @@ function getComponentName(type: mixed): string | null {
|
|||
case REACT_MEMO_TYPE:
|
||||
return getComponentName(type.type);
|
||||
case REACT_BLOCK_TYPE:
|
||||
return getComponentName(type.render);
|
||||
return getComponentName(type._render);
|
||||
case REACT_LAZY_TYPE: {
|
||||
const thenable: LazyComponent<mixed> = (type: any);
|
||||
const resolvedThenable = refineResolvedLazyComponent(thenable);
|
||||
if (resolvedThenable) {
|
||||
return getComponentName(resolvedThenable);
|
||||
const lazyComponent: LazyComponent<any, any> = (type: any);
|
||||
let payload = lazyComponent._payload;
|
||||
let init = lazyComponent._init;
|
||||
try {
|
||||
return getComponentName(init(payload));
|
||||
} catch (x) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue