Skip to content

Commit

Permalink
Refactor Lazy Components to use teh Suspense (and wrap Blocks in Lazy) (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
sebmarkbage authored Mar 23, 2020
1 parent 31a9e39 commit fd61f7e
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 342 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
57 changes: 23 additions & 34 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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: {
Expand Down
50 changes: 35 additions & 15 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -20,6 +21,7 @@ import {
REACT_ELEMENT_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_LAZY_TYPE,
REACT_BLOCK_TYPE,
} from 'shared/ReactSymbols';
import {
Expand Down Expand Up @@ -48,7 +50,6 @@ import {
} from './ReactCurrentFiber';
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
import {StrictMode} from './ReactTypeOfMode';
import {initializeBlockComponentType} from 'shared/ReactLazyComponent';

let didWarnAboutMaps;
let didWarnAboutGenerators;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
34 changes: 17 additions & 17 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -184,7 +180,6 @@ import {
renderDidSuspendDelayIfPossible,
markUnprocessedUpdateTime,
} from './ReactFiberWorkLoop';
import {Resolved} from 'shared/ReactLazyStatusTags';

const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -703,23 +705,18 @@ 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,
) {
// TODO: current can be non-null here even if the component
// 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
Expand Down Expand Up @@ -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));
Expand Down
13 changes: 0 additions & 13 deletions packages/react-reconciler/src/ReactFiberLazyComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
Loading

0 comments on commit fd61f7e

Please sign in to comment.