mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fizz] Implement lazy components and nodes (#21355)
* Implement lazy components * Implement lazy elements / nodes This is used by Flight to encode not yet resolved nodes of any kind.
This commit is contained in:
@@ -207,6 +207,230 @@ describe('ReactDOMFizzServer', () => {
|
||||
return readText(text);
|
||||
}
|
||||
|
||||
// @gate experimental
|
||||
it('should asynchronously load a lazy component', async () => {
|
||||
let resolveA;
|
||||
const LazyA = React.lazy(() => {
|
||||
return new Promise(r => {
|
||||
resolveA = r;
|
||||
});
|
||||
});
|
||||
|
||||
let resolveB;
|
||||
const LazyB = React.lazy(() => {
|
||||
return new Promise(r => {
|
||||
resolveB = r;
|
||||
});
|
||||
});
|
||||
|
||||
function TextWithPunctuation({text, punctuation}) {
|
||||
return <Text text={text + punctuation} />;
|
||||
}
|
||||
// This tests that default props of the inner element is resolved.
|
||||
TextWithPunctuation.defaultProps = {
|
||||
punctuation: '!',
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
|
||||
<div>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyA text="Hello" />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyB text="world" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>,
|
||||
writable,
|
||||
);
|
||||
startWriting();
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<div>Loading...</div>
|
||||
<div>Loading...</div>
|
||||
</div>,
|
||||
);
|
||||
await act(async () => {
|
||||
resolveA({default: Text});
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<div>Hello</div>
|
||||
<div>Loading...</div>
|
||||
</div>,
|
||||
);
|
||||
await act(async () => {
|
||||
resolveB({default: TextWithPunctuation});
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<div>Hello</div>
|
||||
<div>world!</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should client render a boundary if a lazy component rejects', async () => {
|
||||
let rejectComponent;
|
||||
const LazyComponent = React.lazy(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
rejectComponent = reject;
|
||||
});
|
||||
});
|
||||
|
||||
const loggedErrors = [];
|
||||
|
||||
function App({isClient}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
{isClient ? <Text text="Hello" /> : <LazyComponent text="Hello" />}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
|
||||
<App isClient={false} />,
|
||||
writable,
|
||||
{
|
||||
onError(x) {
|
||||
loggedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
startWriting();
|
||||
});
|
||||
expect(loggedErrors).toEqual([]);
|
||||
|
||||
// Attempt to hydrate the content.
|
||||
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App isClient={true} />);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
// We're still loading because we're waiting for the server to stream more content.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
|
||||
|
||||
expect(loggedErrors).toEqual([]);
|
||||
|
||||
const theError = new Error('Test');
|
||||
await act(async () => {
|
||||
rejectComponent(theError);
|
||||
});
|
||||
|
||||
expect(loggedErrors).toEqual([theError]);
|
||||
|
||||
// We haven't ran the client hydration yet.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
|
||||
|
||||
// Now we can client render it instead.
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
// The client rendered HTML is now in place.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
|
||||
|
||||
expect(loggedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should asynchronously load a lazy element', async () => {
|
||||
let resolveElement;
|
||||
const lazyElement = React.lazy(() => {
|
||||
return new Promise(r => {
|
||||
resolveElement = r;
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
{lazyElement}
|
||||
</Suspense>
|
||||
</div>,
|
||||
writable,
|
||||
);
|
||||
startWriting();
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
|
||||
await act(async () => {
|
||||
resolveElement({default: <Text text="Hello" />});
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should client render a boundary if a lazy element rejects', async () => {
|
||||
let rejectElement;
|
||||
const element = <Text text="Hello" />;
|
||||
const lazyElement = React.lazy(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
rejectElement = reject;
|
||||
});
|
||||
});
|
||||
|
||||
const loggedErrors = [];
|
||||
|
||||
function App({isClient}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
{isClient ? element : lazyElement}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
|
||||
<App isClient={false} />,
|
||||
writable,
|
||||
{
|
||||
onError(x) {
|
||||
loggedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
startWriting();
|
||||
});
|
||||
expect(loggedErrors).toEqual([]);
|
||||
|
||||
// Attempt to hydrate the content.
|
||||
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App isClient={true} />);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
// We're still loading because we're waiting for the server to stream more content.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
|
||||
|
||||
expect(loggedErrors).toEqual([]);
|
||||
|
||||
const theError = new Error('Test');
|
||||
await act(async () => {
|
||||
rejectElement(theError);
|
||||
});
|
||||
|
||||
expect(loggedErrors).toEqual([theError]);
|
||||
|
||||
// We haven't ran the client hydration yet.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
|
||||
|
||||
// Now we can client render it instead.
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
// The client rendered HTML is now in place.
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
|
||||
|
||||
expect(loggedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should asynchronously load the suspense boundary', async () => {
|
||||
await act(async () => {
|
||||
|
||||
+18
-4
@@ -102,6 +102,7 @@ import {
|
||||
disableModulePatternComponents,
|
||||
warnAboutDefaultPropsOnFunctionComponents,
|
||||
enableScopeAPI,
|
||||
enableLazyElements,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
@@ -861,10 +862,15 @@ function renderContextProvider(
|
||||
function renderLazyComponent(
|
||||
request: Request,
|
||||
task: Task,
|
||||
type: LazyComponentType<any, any>,
|
||||
lazyComponent: LazyComponentType<any, any>,
|
||||
props: Object,
|
||||
ref: any,
|
||||
): void {
|
||||
throw new Error('Not yet implemented element type.');
|
||||
const payload = lazyComponent._payload;
|
||||
const init = lazyComponent._init;
|
||||
const Component = init(payload);
|
||||
const resolvedProps = resolveDefaultProps(Component, props);
|
||||
return renderElement(request, task, Component, resolvedProps, ref);
|
||||
}
|
||||
|
||||
function renderElement(
|
||||
@@ -1018,8 +1024,16 @@ function renderNodeDestructive(
|
||||
'Render them conditionally so that they only appear on the client render.',
|
||||
);
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_LAZY_TYPE:
|
||||
throw new Error('Not yet implemented node type.');
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (enableLazyElements) {
|
||||
const lazyNode: LazyComponentType<any, any> = (node: any);
|
||||
const payload = lazyNode._payload;
|
||||
const init = lazyNode._init;
|
||||
const resolvedNode = init(payload);
|
||||
renderNodeDestructive(request, task, resolvedNode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray(node)) {
|
||||
|
||||
Reference in New Issue
Block a user