mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Flight Reply] Add undefined and Iterable Support (#26365)
These were recently added to ReactClientValue and so needs to be reflected in ReactServerValue too.
This commit is contained in:
+17
-5
@@ -15,6 +15,7 @@ import {
|
||||
REACT_ELEMENT_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
getIteratorFn,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import {
|
||||
@@ -46,6 +47,7 @@ export type ReactServerValue =
|
||||
| number
|
||||
| symbol
|
||||
| null
|
||||
| void
|
||||
| Iterable<ReactServerValue>
|
||||
| Array<ReactServerValue>
|
||||
| ReactServerObject
|
||||
@@ -69,6 +71,10 @@ function serializeSymbolReference(name: string): string {
|
||||
return '$S' + name;
|
||||
}
|
||||
|
||||
function serializeUndefined(): string {
|
||||
return '$undefined';
|
||||
}
|
||||
|
||||
function escapeStringValue(value: string): string {
|
||||
if (value[0] === '$') {
|
||||
// We need to escape $ prefixed strings since we use those to encode
|
||||
@@ -154,6 +160,12 @@ export function processReply(
|
||||
);
|
||||
return serializePromiseID(promiseId);
|
||||
}
|
||||
if (!isArray(value)) {
|
||||
const iteratorFn = getIteratorFn(value);
|
||||
if (iteratorFn) {
|
||||
return Array.from((value: any));
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (value !== null && !isArray(value)) {
|
||||
@@ -208,14 +220,14 @@ export function processReply(
|
||||
return escapeStringValue(value);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value === 'boolean' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'undefined'
|
||||
) {
|
||||
if (typeof value === 'boolean' || typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
return serializeUndefined();
|
||||
}
|
||||
|
||||
if (typeof value === 'function') {
|
||||
const metaData = knownServerReferences.get(value);
|
||||
if (metaData !== undefined) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
global.TextDecoder = require('util').TextDecoder;
|
||||
|
||||
// let serverExports;
|
||||
let webpackServerMap;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
|
||||
describe('ReactFlightDOMReply', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
const WebpackMock = require('./utils/WebpackMock');
|
||||
// serverExports = WebpackMock.serverExports;
|
||||
webpackServerMap = WebpackMock.webpackServerMap;
|
||||
ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
|
||||
ReactServerDOMClient = require('react-server-dom-webpack/client');
|
||||
});
|
||||
|
||||
it('can pass undefined as a reply', async () => {
|
||||
const body = await ReactServerDOMClient.encodeReply(undefined);
|
||||
const missing = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
webpackServerMap,
|
||||
);
|
||||
expect(missing).toBe(undefined);
|
||||
|
||||
const body2 = await ReactServerDOMClient.encodeReply({
|
||||
array: [undefined, null, undefined],
|
||||
prop: undefined,
|
||||
});
|
||||
const object = await ReactServerDOMServer.decodeReply(
|
||||
body2,
|
||||
webpackServerMap,
|
||||
);
|
||||
expect(object.array.length).toBe(3);
|
||||
expect(object.array[0]).toBe(undefined);
|
||||
expect(object.array[1]).toBe(null);
|
||||
expect(object.array[3]).toBe(undefined);
|
||||
expect(object.prop).toBe(undefined);
|
||||
// These should really be true but our deserialization doesn't currently deal with it.
|
||||
expect('3' in object.array).toBe(false);
|
||||
expect('prop' in object).toBe(false);
|
||||
});
|
||||
|
||||
it('can pass an iterable as a reply', async () => {
|
||||
const body = await ReactServerDOMClient.encodeReply({
|
||||
[Symbol.iterator]: function* () {
|
||||
yield 'A';
|
||||
yield 'B';
|
||||
yield 'C';
|
||||
},
|
||||
});
|
||||
const iterable = await ReactServerDOMServer.decodeReply(
|
||||
body,
|
||||
webpackServerMap,
|
||||
);
|
||||
const items = [];
|
||||
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
|
||||
for (const item of iterable) {
|
||||
items.push(item);
|
||||
}
|
||||
expect(items).toEqual(['A', 'B', 'C']);
|
||||
});
|
||||
});
|
||||
@@ -397,6 +397,11 @@ function parseModelString(
|
||||
key,
|
||||
);
|
||||
}
|
||||
case 'u': {
|
||||
// matches "$undefined"
|
||||
// Special encoding for `undefined` which can't be serialized as JSON otherwise.
|
||||
return undefined;
|
||||
}
|
||||
default: {
|
||||
// We assume that anything else is a reference ID.
|
||||
const id = parseInt(value.substring(1), 16);
|
||||
|
||||
Reference in New Issue
Block a user