Add Lazy Elements Behind a Flag (#19033)
We really needed this for Flight before as well but we got away with it because Blocks were lazy but with the removal of Blocks, we'll need this to ensure that we can lazily stream in part of the content. Luckily LazyComponent isn't really just a Component. It's just a generic type that can resolve into anything kind of like a Promise. So we can use that to resolve elements just like we can components. This allows keys and props to become lazy as well. To accomplish this, we suspend during reconciliation. This causes us to not be able to render siblings because we don't know if the keys will reconcile. For initial render we could probably special case this and just render a lazy component fiber. Throwing in reconciliation didn't work correctly with direct nested siblings of a Suspense boundary before but it does now so it depends on new reconciler.
This commit is contained in:
parent
4985bb0a80
commit
518ce9c25f
|
@ -33,7 +33,11 @@ import {
|
|||
Block,
|
||||
} from './ReactWorkTags';
|
||||
import invariant from 'shared/invariant';
|
||||
import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
warnAboutStringRefs,
|
||||
enableBlocksAPI,
|
||||
enableLazyElements,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
createWorkInProgress,
|
||||
|
@ -532,6 +536,13 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
created.return = returnFiber;
|
||||
return created;
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return createChild(returnFiber, init(payload), lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -602,6 +613,13 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return updateSlot(returnFiber, oldFiber, init(payload), lanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -663,6 +681,18 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
) || null;
|
||||
return updatePortal(returnFiber, matchedFiber, newChild, lanes);
|
||||
}
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return updateFromMap(
|
||||
existingChildren,
|
||||
returnFiber,
|
||||
newIdx,
|
||||
init(payload),
|
||||
lanes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -720,6 +750,15 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
key,
|
||||
);
|
||||
break;
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = child._payload;
|
||||
const init = (child._init: any);
|
||||
warnOnInvalidKey(init(payload), knownKeys, returnFiber);
|
||||
break;
|
||||
}
|
||||
// We intentionally fallthrough here if enableLazyElements is not on.
|
||||
// eslint-disable-next-lined no-fallthrough
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1276,6 +1315,18 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
lanes,
|
||||
),
|
||||
);
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
// TODO: This function is supposed to be non-recursive.
|
||||
return reconcileChildFibers(
|
||||
returnFiber,
|
||||
currentFirstChild,
|
||||
init(payload),
|
||||
lanes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,11 @@ import {
|
|||
Block,
|
||||
} from './ReactWorkTags';
|
||||
import invariant from 'shared/invariant';
|
||||
import {warnAboutStringRefs, enableBlocksAPI} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
warnAboutStringRefs,
|
||||
enableBlocksAPI,
|
||||
enableLazyElements,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
createWorkInProgress,
|
||||
|
@ -542,6 +546,13 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
created.return = returnFiber;
|
||||
return created;
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return createChild(returnFiber, init(payload), expirationTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -627,6 +638,18 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return updateSlot(
|
||||
returnFiber,
|
||||
oldFiber,
|
||||
init(payload),
|
||||
expirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -709,6 +732,18 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
expirationTime,
|
||||
);
|
||||
}
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
return updateFromMap(
|
||||
existingChildren,
|
||||
returnFiber,
|
||||
newIdx,
|
||||
init(payload),
|
||||
expirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(newChild) || getIteratorFn(newChild)) {
|
||||
|
@ -772,6 +807,15 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
key,
|
||||
);
|
||||
break;
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = child._payload;
|
||||
const init = (child._init: any);
|
||||
warnOnInvalidKey(init(payload), knownKeys, returnFiber);
|
||||
break;
|
||||
}
|
||||
// We intentionally fallthrough here if enableLazyElements is not on.
|
||||
// eslint-disable-next-lined no-fallthrough
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1349,6 +1393,18 @@ function ChildReconciler(shouldTrackSideEffects) {
|
|||
expirationTime,
|
||||
),
|
||||
);
|
||||
case REACT_LAZY_TYPE:
|
||||
if (enableLazyElements) {
|
||||
const payload = newChild._payload;
|
||||
const init = newChild._init;
|
||||
// TODO: This function is supposed to be non-recursive.
|
||||
return reconcileChildFibers(
|
||||
returnFiber,
|
||||
currentFirstChild,
|
||||
init(payload),
|
||||
expirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1273,4 +1273,80 @@ describe('ReactLazy', () => {
|
|||
|
||||
expect(componentStackMessage).toContain('in Lazy');
|
||||
});
|
||||
|
||||
// @gate enableLazyElements && enableNewReconciler
|
||||
it('mount and reorder lazy elements', async () => {
|
||||
class Child extends React.Component {
|
||||
componentDidMount() {
|
||||
Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
Scheduler.unstable_yieldValue('Did update: ' + this.props.label);
|
||||
}
|
||||
render() {
|
||||
return <Text text={this.props.label} />;
|
||||
}
|
||||
}
|
||||
|
||||
const lazyChildA = lazy(() => {
|
||||
Scheduler.unstable_yieldValue('Init A');
|
||||
return fakeImport(<Child key="A" label="A" />);
|
||||
});
|
||||
const lazyChildB = lazy(() => {
|
||||
Scheduler.unstable_yieldValue('Init B');
|
||||
return fakeImport(<Child key="B" label="B" />);
|
||||
});
|
||||
const lazyChildA2 = lazy(() => {
|
||||
Scheduler.unstable_yieldValue('Init A2');
|
||||
return fakeImport(<Child key="A" label="a" />);
|
||||
});
|
||||
const lazyChildB2 = lazy(() => {
|
||||
Scheduler.unstable_yieldValue('Init B2');
|
||||
return fakeImport(<Child key="B" label="b" />);
|
||||
});
|
||||
|
||||
function Parent({swap}) {
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
{swap ? [lazyChildB2, lazyChildA2] : [lazyChildA, lazyChildB]}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<Parent swap={false} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Init A', 'Loading...']);
|
||||
expect(root).not.toMatchRenderedOutput('AB');
|
||||
|
||||
await lazyChildA;
|
||||
// We need to flush to trigger the B to load.
|
||||
expect(Scheduler).toFlushAndYield(['Init B']);
|
||||
await lazyChildB;
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'A',
|
||||
'B',
|
||||
'Did mount: A',
|
||||
'Did mount: B',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
|
||||
// Swap the position of A and B
|
||||
root.update(<Parent swap={true} />);
|
||||
expect(Scheduler).toFlushAndYield(['Init B2', 'Loading...']);
|
||||
await lazyChildB2;
|
||||
// We need to flush to trigger the second one to load.
|
||||
expect(Scheduler).toFlushAndYield(['Init A2', 'Loading...']);
|
||||
await lazyChildA2;
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'b',
|
||||
'a',
|
||||
'Did update: b',
|
||||
'Did update: a',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('ba');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,6 +41,7 @@ export const enableSelectiveHydration = __EXPERIMENTAL__;
|
|||
|
||||
// Flight experiments
|
||||
export const enableBlocksAPI = __EXPERIMENTAL__;
|
||||
export const enableLazyElements = __EXPERIMENTAL__;
|
||||
|
||||
// Only used in www builds.
|
||||
export const enableSchedulerDebugging = false;
|
||||
|
|
|
@ -18,6 +18,7 @@ export const enableSchedulerTracing = __PROFILE__;
|
|||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableBlocksAPI = false;
|
||||
export const enableLazyElements = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
export const debugRenderPhaseSideEffectsForStrictMode = true;
|
||||
export const disableJavaScriptURLs = false;
|
||||
|
|
|
@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
|
|||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableBlocksAPI = false;
|
||||
export const enableLazyElements = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
|
|
|
@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
|
|||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableBlocksAPI = false;
|
||||
export const enableLazyElements = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
|
|
|
@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
|
|||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableBlocksAPI = false;
|
||||
export const enableLazyElements = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
|
|
|
@ -20,6 +20,7 @@ export const enableSchedulerTracing = __PROFILE__;
|
|||
export const enableSuspenseServerRenderer = false;
|
||||
export const enableSelectiveHydration = false;
|
||||
export const enableBlocksAPI = false;
|
||||
export const enableLazyElements = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
|
|
|
@ -20,6 +20,7 @@ export const enableSchedulerTracing = false;
|
|||
export const enableSuspenseServerRenderer = true;
|
||||
export const enableSelectiveHydration = true;
|
||||
export const enableBlocksAPI = true;
|
||||
export const enableLazyElements = false;
|
||||
export const disableJavaScriptURLs = true;
|
||||
export const disableInputAttributeSyncing = false;
|
||||
export const enableSchedulerDebugging = false;
|
||||
|
|
|
@ -50,6 +50,7 @@ export const enableSuspenseServerRenderer = true;
|
|||
export const enableSelectiveHydration = true;
|
||||
|
||||
export const enableBlocksAPI = true;
|
||||
export const enableLazyElements = true;
|
||||
|
||||
export const disableJavaScriptURLs = true;
|
||||
|
||||
|
|
Loading…
Reference in New Issue