[Fizz] Fragments and Iterable support (#21228)
This commit is contained in:
parent
933880b454
commit
343710c923
|
@ -351,10 +351,12 @@ describe('ReactDOMFizzServer', () => {
|
|||
await act(async () => {
|
||||
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
|
||||
<Suspense fallback={<Text text="Loading A..." />}>
|
||||
<Text text="This will show A: " />
|
||||
<div>
|
||||
<AsyncText text="A" />
|
||||
</div>
|
||||
<>
|
||||
<Text text="This will show A: " />
|
||||
<div>
|
||||
<AsyncText text="A" />
|
||||
</div>
|
||||
</>
|
||||
</Suspense>,
|
||||
writableA,
|
||||
{
|
||||
|
@ -432,11 +434,11 @@ describe('ReactDOMFizzServer', () => {
|
|||
}
|
||||
|
||||
function AsyncPath({id}) {
|
||||
return <path id={readText(id)}>{[]}</path>;
|
||||
return <path id={readText(id)} />;
|
||||
}
|
||||
|
||||
function AsyncMi({id}) {
|
||||
return <mi id={readText(id)}>{[]}</mi>;
|
||||
return <mi id={readText(id)} />;
|
||||
}
|
||||
|
||||
function App() {
|
||||
|
@ -601,7 +603,7 @@ describe('ReactDOMFizzServer', () => {
|
|||
// @gate experimental
|
||||
it('can stream into an SVG container', async () => {
|
||||
function AsyncPath({id}) {
|
||||
return <path id={readText(id)}>{[]}</path>;
|
||||
return <path id={readText(id)} />;
|
||||
}
|
||||
|
||||
function App() {
|
||||
|
|
|
@ -62,6 +62,12 @@ import {
|
|||
REACT_PORTAL_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_SUSPENSE_TYPE,
|
||||
REACT_LEGACY_HIDDEN_TYPE,
|
||||
REACT_DEBUG_TRACING_MODE_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
REACT_PROFILER_TYPE,
|
||||
REACT_SUSPENSE_LIST_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
|
@ -521,6 +527,8 @@ const didWarnAboutContextTypeOnFunctionComponent = {};
|
|||
const didWarnAboutGetDerivedStateOnFunctionComponent = {};
|
||||
let didWarnAboutReassigningProps = false;
|
||||
const didWarnAboutDefaultPropsOnFunctionComponent = {};
|
||||
let didWarnAboutGenerators = false;
|
||||
let didWarnAboutMaps = false;
|
||||
|
||||
// This would typically be a function component but we still support module pattern
|
||||
// components for some reason.
|
||||
|
@ -701,10 +709,67 @@ function renderElement(
|
|||
}
|
||||
} else if (typeof type === 'string') {
|
||||
renderHostElement(request, task, type, props);
|
||||
} else if (type === REACT_SUSPENSE_TYPE) {
|
||||
renderSuspenseBoundary(request, task, props);
|
||||
} else {
|
||||
throw new Error('Not yet implemented element type.');
|
||||
switch (type) {
|
||||
// TODO: LegacyHidden acts the same as a fragment. This only works
|
||||
// because we currently assume that every instance of LegacyHidden is
|
||||
// accompanied by a host component wrapper. In the hidden mode, the host
|
||||
// component is given a `hidden` attribute, which ensures that the
|
||||
// initial HTML is not visible. To support the use of LegacyHidden as a
|
||||
// true fragment, without an extra DOM node, we would have to hide the
|
||||
// initial HTML in some other way.
|
||||
// TODO: Add REACT_OFFSCREEN_TYPE here too with the same capability.
|
||||
case REACT_LEGACY_HIDDEN_TYPE:
|
||||
case REACT_DEBUG_TRACING_MODE_TYPE:
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
case REACT_PROFILER_TYPE:
|
||||
case REACT_SUSPENSE_LIST_TYPE: // TODO: SuspenseList should control the boundaries.
|
||||
case REACT_FRAGMENT_TYPE: {
|
||||
renderNodeDestructive(request, task, props.children);
|
||||
break;
|
||||
}
|
||||
case REACT_SUSPENSE_TYPE: {
|
||||
renderSuspenseBoundary(request, task, props);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error('Not yet implemented element type.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateIterable(iterable, iteratorFn: Function): void {
|
||||
if (__DEV__) {
|
||||
// We don't support rendering Generators because it's a mutation.
|
||||
// See https://github.com/facebook/react/issues/12995
|
||||
if (
|
||||
typeof Symbol === 'function' &&
|
||||
// $FlowFixMe Flow doesn't know about toStringTag
|
||||
iterable[Symbol.toStringTag] === 'Generator'
|
||||
) {
|
||||
if (!didWarnAboutGenerators) {
|
||||
console.error(
|
||||
'Using Generators as children is unsupported and will likely yield ' +
|
||||
'unexpected results because enumerating a generator mutates it. ' +
|
||||
'You may convert it to an array with `Array.from()` or the ' +
|
||||
'`[...spread]` operator before rendering. Keep in mind ' +
|
||||
'you might need to polyfill these features for older browsers.',
|
||||
);
|
||||
}
|
||||
didWarnAboutGenerators = true;
|
||||
}
|
||||
|
||||
// Warn about using Maps as children
|
||||
if ((iterable: any).entries === iteratorFn) {
|
||||
if (!didWarnAboutMaps) {
|
||||
console.error(
|
||||
'Using Maps as children is not supported. ' +
|
||||
'Use an array of keyed ReactElements instead.',
|
||||
);
|
||||
}
|
||||
didWarnAboutMaps = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -756,7 +821,30 @@ function renderNodeDestructive(
|
|||
|
||||
const iteratorFn = getIteratorFn(node);
|
||||
if (iteratorFn) {
|
||||
throw new Error('Not yet implemented node type.');
|
||||
if (__DEV__) {
|
||||
validateIterable(node, iteratorFn());
|
||||
}
|
||||
const iterator = iteratorFn.call(node);
|
||||
if (iterator) {
|
||||
let step = iterator.next();
|
||||
// If there are not entries, we need to push an empty so we start by checking that.
|
||||
if (!step.done) {
|
||||
do {
|
||||
// Recursively render the rest. We need to use the non-destructive form
|
||||
// so that we can safely pop back up and render the sibling if something
|
||||
// suspends.
|
||||
renderNode(request, task, step.value);
|
||||
step = iterator.next();
|
||||
} while (!step.done);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pushEmpty(
|
||||
task.blockedSegment.chunks,
|
||||
request.responseState,
|
||||
task.assignID,
|
||||
);
|
||||
task.assignID = null;
|
||||
}
|
||||
|
||||
const childString = Object.prototype.toString.call(node);
|
||||
|
@ -805,6 +893,7 @@ function renderNodeDestructive(
|
|||
|
||||
// Any other type is assumed to be empty.
|
||||
pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
|
||||
task.assignID = null;
|
||||
}
|
||||
|
||||
function spawnNewSuspendedTask(
|
||||
|
|
Loading…
Reference in New Issue