跳到主要内容

作用

导出的类型

服务器引用

export opaque type ServerReference<T> = T;

调用服务器回调

export type CallServerCallback = <A, T>(id: any, args: A) => Promise<T>;

编码表单动作回调

export type EncodeFormActionCallback = <A>(
id: any,
args: Promise<A>,
) => ReactCustomFormAction;

服务器引用ID

export type ServerReferenceId = any;

React 服务器值

// Serializable values
// 可序列化的值
export type ReactServerValue =
// References are passed by their value
// 引用是按其值传递的
| ServerReference<any>
// The rest are passed as is. Sub-types can be passed in but lose their
// subtype, so the receiver can only accept once of these.
// 其余的按原样传递。子类型可以传入,但会失去其子类型,因此接收方只能接受一次这些。
| string
| boolean
| number
| null
| void
| bigint
| $AsyncIterable<ReactServerValue, ReactServerValue, void>
| $AsyncIterator<ReactServerValue, ReactServerValue, void>
| Iterable<ReactServerValue>
| Iterator<ReactServerValue>
| Array<ReactServerValue>
| Map<ReactServerValue, ReactServerValue>
| Set<ReactServerValue>
| FormData
| Date
| ReactServerObject
| Promise<ReactServerValue>; // Thenable<ReactServerValue>

查找源映射 URL 回调

export type FindSourceMapURLCallback = (
fileName: string,
environmentName: string,
) => null | string;

处理回复

export function processReply(
root: ReactServerValue,
formFieldPrefix: string,
temporaryReferences: void | TemporaryReferenceSet,
// resolve: (string | FormData) => void,
resolve: (resolve: string | FormData) => void,
reject: (error: mixed) => void,
): (reason: mixed) => void {
let nextPartId = 1;
let pendingParts = 0;
let formData: null | FormData = null;
const writtenObjects: WeakMap<Reference, string> = new WeakMap();
let modelRoot: null | ReactServerValue = root;

if (__DEV__) {
// We use eval to create fake function stacks which includes Component stacks.
// A warning would be noise if you used Flight without Components and don't encounter
// errors. We're warning eagerly so that you configure your environment accordingly
// before you encounter an error.
// 我们使用 eval 来创建包含组件堆栈的假函数堆栈。
// 如果你使用 Flight 而不使用组件且没有遇到错误,警告会显得多余。
// 我们会提前发出警告,以便你在遇到错误之前配置好你的环境。
checkEvalAvailabilityOnceDev();
}

function serializeTypedArray(
tag: string,
// typedArray: $ArrayBufferView,
typedArray: ArrayBufferView,
): string {
const blob = new Blob([
// We should be able to pass the buffer straight through but Node < 18 treat
// multi-byte array blobs differently so we first convert it to single-byte.
// 我们应该能够直接传递缓冲区,但 Node < 18 对多字节数组数据块的处理不同,所以我们首先将其转换为单字节。
new Uint8Array(
typedArray.buffer,
typedArray.byteOffset,
typedArray.byteLength,
),
]);
const blobId = nextPartId++;
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + blobId, blob);
return '$' + tag + blobId.toString(16);
}

function serializeBinaryReader(reader: any): string {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,使我们能够流式传输此值。
formData = new FormData();
}
const data = formData;

pendingParts++;
const streamId = nextPartId++;

const buffer = [];

function progress(entry: // {done: boolean, value: ReactServerValue, ...}
{
done: boolean;
value: ReactServerValue;
}) {
if (entry.done) {
const blobId = nextPartId++;
data.append(formFieldPrefix + blobId, new Blob(buffer));
data.append(
formFieldPrefix + streamId,
'"$o' + blobId.toString(16) + '"',
);
// 关闭信号
data.append(formFieldPrefix + streamId, 'C'); // Close signal
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
buffer.push(entry.value);
reader.read(new Uint8Array(1024)).then(progress, reject);
}
}
reader.read(new Uint8Array(1024)).then(progress, reject);

return '$r' + streamId.toString(16);
}

function serializeReader(reader: ReadableStreamReader): string {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,以便我们能够流式传输此值。
formData = new FormData();
}
const data = formData;

pendingParts++;
const streamId = nextPartId++;

function progress(entry: // {done: boolean, value: ReactServerValue, ...}
{
done: boolean;
value: ReactServerValue;
}) {
if (entry.done) {
// 关闭信号
data.append(formFieldPrefix + streamId, 'C'); // Close signal
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
try {
const partJSON: string = JSON.stringify(entry.value, resolveToJSON);
data.append(formFieldPrefix + streamId, partJSON);
reader.read().then(progress, reject);
} catch (x) {
reject(x);
}
}
}
reader.read().then(progress, reject);

return '$R' + streamId.toString(16);
}

function serializeReadableStream(stream: ReadableStream): string {
// Detect if this is a BYOB stream. BYOB streams should be able to be read as bytes on the
// receiving side. For binary streams, we serialize them as plain Blobs.
// 检测这是否是一个 BYOB 流。BYOB 流在接收端应能够以字节形式读取。
// 对于二进制流,我们将它们序列化为普通的 Blob。
let binaryReader;
try {
binaryReader = stream.getReader({ mode: 'byob' });
} catch (x) {
return serializeReader(stream.getReader());
}
return serializeBinaryReader(binaryReader);
}

function serializeAsyncIterable(
// iterable: $AsyncIterable<ReactServerValue, ReactServerValue, void>,
iterable: AsyncIterable<ReactServerValue, ReactServerValue, void>,
// iterator: $AsyncIterator<ReactServerValue, ReactServerValue, void>,
iterator: AsyncIterator<ReactServerValue, ReactServerValue, void>,
): string {
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,以便我们能够流式传输此值。
formData = new FormData();
}
const data = formData;

pendingParts++;
const streamId = nextPartId++;

// Generators/Iterators are Iterables but they're also their own iterator
// functions. If that's the case, we treat them as single-shot. Otherwise,
// we assume that this iterable might be a multi-shot and allow it to be
// iterated more than once on the receiving server.
// 生成器/迭代器是可迭代对象,但它们也同时是自己的迭代器函数。如果是这种情况,我们
// 将它们视为一次性使用。否则,我们假设这个可迭代对象可能是多次使用的,并允许它在接
// 收服务器上被迭代多次。
const isIterator = iterable === iterator;

// There's a race condition between when the stream is aborted and when the promise
// resolves so we track whether we already aborted it to avoid writing twice.
// 与流不同,最后一个值可能不是未定义的。如果不是,我们将对其进行概述并在结束指令中
// 对其进行编码引用。
function progress(
entry: // | {done: false, +value: ReactServerValue, ...}
| { done: false; value: ReactServerValue }
// | {done: true, +value: ReactServerValue, ...},
| { done: true; value: ReactServerValue },
) {
if (entry.done) {
if (entry.value === undefined) {
// 关闭信号
data.append(formFieldPrefix + streamId, 'C'); // Close signal
} else {
// Unlike streams, the last value may not be undefined. If it's not
// we outline it and encode a reference to it in the closing instruction.
// 与流不同,最后一个值可能不是未定义的。如果不是,我们将对其进行概述并在结
// 束指令中对其进行编码引用。
try {
const partJSON: string = JSON.stringify(entry.value, resolveToJSON);
// 关闭信号
data.append(formFieldPrefix + streamId, 'C' + partJSON); // Close signal
} catch (x) {
reject(x);
return;
}
}
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} else {
try {
const partJSON: string = JSON.stringify(entry.value, resolveToJSON);
data.append(formFieldPrefix + streamId, partJSON);
iterator.next().then(progress, reject);
} catch (x) {
reject(x);
return;
}
}
}

iterator.next().then(progress, reject);
return '$' + (isIterator ? 'x' : 'X') + streamId.toString(16);
}

function resolveToJSON(
this: // | {+[key: string | number]: ReactServerValue}
| { [key: string | number]: ReactServerValue }
// | $ReadOnlyArray<ReactServerValue>,
| ReadOnlyArray<ReactServerValue>,
key: string,
value: ReactServerValue,
): ReactJSONValue {
const parent = this;

if (__DEV__) {
if (key === __PROTO__) {
console.error(
'Expected not to serialize an object with own property `__proto__`. When parsed this property will be omitted.%s',
describeObjectForErrorMessage(parent, key),
);
}
}

// Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
// 确保在 `value` 传给我们之前,`parent[key]` 没有被 JSON 化
if (__DEV__) {
const originalValue = parent[key];
if (
typeof originalValue === 'object' &&
originalValue !== value &&
!(originalValue instanceof Date)
) {
if (objectName(originalValue) !== 'Object') {
console.error(
'Only plain objects can be passed to Server Functions from the Client. ' +
'%s objects are not supported.%s',
objectName(originalValue),
describeObjectForErrorMessage(parent, key),
);
} else {
console.error(
'Only plain objects can be passed to Server Functions from the Client. ' +
'Objects with toJSON methods are not supported. Convert it manually ' +
'to a simple value before passing it to props.%s',
describeObjectForErrorMessage(parent, key),
);
}
}
}

if (value === null) {
return null;
}

if (typeof value === 'object') {
switch ((value as any).$$typeof) {
case REACT_ELEMENT_TYPE: {
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
// 待办事项:如果属性名包含冒号,我们不去重。改为转义。
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
// 如果父对象有引用,我们可以通过父对象内的属性名间接引用该对象。
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
// 存储此对象,以便服务器以后在响应中可以引用它。
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
// This element is the root of a serializeModel call (e.g. JSX
// passed directly to encodeReply, or a promise that resolved to
// JSX). It was already registered as a temporary reference by
// serializeModel so we just need to emit the marker.
// 这个元素是 serializeModel 调用的根(例如 JSX
// 直接传递给 encodeReply,或解析为 JSX 的 promise)。它已经被
// serializeModel 注册为临时引用,所以我们只需要发出标记。
if (temporaryReferences !== undefined && modelRoot === value) {
modelRoot = null;
return serializeTemporaryReferenceMarker();
}
throw new Error(
'React Element cannot be passed to Server Functions from the Client without a ' +
'temporary reference set. Pass a TemporaryReferenceSet to the options.' +
(__DEV__ ? describeObjectForErrorMessage(parent, key) : ''),
);
}
case REACT_LAZY_TYPE: {
// Resolve lazy as if it wasn't here. In the future this will be encoded as a Promise.
// 解决 lazy,就好像它不存在一样。将来这将被编码为一个 Promise。
const lazy: LazyComponent<any, any> = value as any;
const payload = lazy._payload;
const init = lazy._init;
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,使我们能够流式传输此值。
formData = new FormData();
}
pendingParts++;
try {
const resolvedModel = init(payload);
// We always outline this as a separate part even though we could inline it
// because it ensures a more deterministic encoding.
// 我们总是将这部分单独列出,即使我们可以内联它
// 因为它确保了更确定性的编码。
const lazyId = nextPartId++;
const partJSON = serializeModel(resolvedModel, lazyId);
const data: FormData = formData;
data.append(formFieldPrefix + lazyId, partJSON);
return serializeByValueID(lazyId);
} catch (x) {
if (
typeof x === 'object' &&
x !== null &&
typeof x.then === 'function'
) {
// Suspended
// 暂停
pendingParts++;
const lazyId = nextPartId++;
const thenable: Thenable<any> = x as any;
const retry = function () {
// While the first promise resolved, its value isn't necessarily what we'll
// resolve into because we might suspend again.
// 虽然第一个 promise 已经解决,但它的值不一定是我们最终会得到的
// 值,因为我们可能会再次暂停。
try {
const partJSON = serializeModel(value, lazyId);
const data: FormData = formData;
data.append(formFieldPrefix + lazyId, partJSON);
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} catch (reason) {
reject(reason);
}
};
thenable.then(retry, retry);
return serializeByValueID(lazyId);
} else {
// In the future we could consider serializing this as an error
// that throws on the server instead.
// 将来我们可以考虑把它序列化为一个会在服务器上抛出的错误。
reject(x);
return null;
}
} finally {
pendingParts--;
}
}
}

const existingReference = writtenObjects.get(value);

if (typeof value.then === 'function') {
if (existingReference !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
// 这是我们当前正在发出的 ID,所以我们只需写一次,但如果我们再次发现它,
// 我们就通过 ID 引用它。
modelRoot = null;
} else {
// We've already emitted this as an outlined object, so we can
// just refer to that by its existing ID.
// 我们已经把它作为一个轮廓对象发出,所以我们可以
// 直接通过它现有的 ID 引用它。
return existingReference;
}
}

// We assume that any object with a .then property is a "Thenable" type,
// or a Promise type. Either of which can be represented by a Promise.
// 我们假设任何具有 .then 属性的对象都是一个“Thenable”类型,
// 或者是一个 Promise 类型。它们中的任何一个都可以用 Promise 表示。
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,使我们能够流式传输此值。
formData = new FormData();
}
pendingParts++;
const promiseId = nextPartId++;
const promiseReference = serializePromiseID(promiseId);
writtenObjects.set(value, promiseReference);
const thenable: Thenable<any> = value as any;
thenable.then(
partValue => {
try {
const previousReference = writtenObjects.get(partValue);
let partJSON;
if (previousReference !== undefined) {
partJSON = JSON.stringify(previousReference);
} else {
partJSON = serializeModel(partValue, promiseId);
}
const data: FormData = formData;
data.append(formFieldPrefix + promiseId, partJSON);
pendingParts--;
if (pendingParts === 0) {
resolve(data);
}
} catch (reason) {
reject(reason);
}
},
// In the future we could consider serializing this as an error
// that throws on the server instead.
// 将来我们可以考虑把它序列化为一个会在服务器上抛出的错误。
reject,
);
return promiseReference;
}

if (existingReference !== undefined) {
if (modelRoot === value) {
// This is the ID we're currently emitting so we need to write it
// once but if we discover it again, we refer to it by id.
// 这是我们当前正在发出的 ID,所以我们只需写一次,但如果我们再次发现它,我
// 们就通过 ID 来引用它。
modelRoot = null;
} else {
// We've already emitted this as an outlined object, so we can
// just refer to that by its existing ID.
// 我们已经把它作为一个轮廓化对象发出,所以我们可以直接通过它现有的 ID 来引用它。
return existingReference;
}
} else if (key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
// 待办事项:如果属性名包含冒号,我们不去重。改为转义。
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
// 如果父对象有引用,我们可以通过父对象内的属性名称间接引用该对象。
const reference = parentReference + ':' + key;
writtenObjects.set(value, reference);
if (temporaryReferences !== undefined) {
// Store this object so that the server can refer to it later in responses.
// 存储此对象,以便服务器以后在响应中可以引用它。
writeTemporaryReference(temporaryReferences, reference, value);
}
}
}

if (isArray(value)) {
return value;
}
// TODO: Should we the Object.prototype.toString.call() to test for cross-realm objects?
// TODO:我们是否应该使用 Object.prototype.toString.call() 来测试跨域对象?
if (value instanceof FormData) {
if (formData === null) {
// Upgrade to use FormData to allow us to use rich objects as its values.
// 升级以使用 FormData,以便我们可以将丰富的对象作为其值使用。
formData = new FormData();
}
const data: FormData = formData;
const refId = nextPartId++;
// Copy all the form fields with a prefix for this reference.
// 将此引用的所有表单字段复制并加上前缀。
// These must come first in the form order because we assume that all the
// fields are available before this is referenced.
// 这些字段必须在表单顺序中最先出现,因为我们假设在引用它之前所有字段都是可用的。
const prefix = formFieldPrefix + refId + '_';
value.forEach((originalValue: string | File, originalKey: string) => {
data.append(prefix + originalKey, originalValue);
});
return serializeFormDataReference(refId);
}
if (value instanceof Map) {
const mapId = nextPartId++;
const partJSON = serializeModel(Array.from(value), mapId);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + mapId, partJSON);
return serializeMapID(mapId);
}
if (value instanceof Set) {
const setId = nextPartId++;
const partJSON = serializeModel(Array.from(value), setId);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + setId, partJSON);
return serializeSetID(setId);
}

if (value instanceof ArrayBuffer) {
const blob = new Blob([value]);
const blobId = nextPartId++;
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + blobId, blob);
return '$' + 'A' + blobId.toString(16);
}
if (value instanceof Int8Array) {
// char
// 字符
return serializeTypedArray('O', value);
}
if (value instanceof Uint8Array) {
// unsigned char
// 无符号字符
return serializeTypedArray('o', value);
}
if (value instanceof Uint8ClampedArray) {
// unsigned clamped char
// 无符号饱和字符
return serializeTypedArray('U', value);
}
if (value instanceof Int16Array) {
// sort
// 短数字
return serializeTypedArray('S', value);
}
if (value instanceof Uint16Array) {
// unsigned short
// 无符号短数字
return serializeTypedArray('s', value);
}
if (value instanceof Int32Array) {
// long
// 大数字
return serializeTypedArray('L', value);
}
if (value instanceof Uint32Array) {
// unsigned long
// 无符号大数字
return serializeTypedArray('l', value);
}
if (value instanceof Float32Array) {
// float
// 浮点数
return serializeTypedArray('G', value);
}
if (value instanceof Float64Array) {
// double
// 双浮点数
return serializeTypedArray('g', value);
}
if (value instanceof BigInt64Array) {
// number
// 数字
return serializeTypedArray('M', value);
}
if (value instanceof BigUint64Array) {
// unsigned number
// 无符号数字
// We use "m" instead of "n" since JSON can start with "null"
// 我们使用“m”而不是“n”,因为 JSON 可以以“null”开头
return serializeTypedArray('m', value);
}
if (value instanceof DataView) {
return serializeTypedArray('V', value);
}
// TODO: Blob is not available in old Node/browsers. Remove the typeof check later.
// 待办事项:在旧版本 Node/浏览器中不可用 Blob。稍后移除 typeof 检查。
if (typeof Blob === 'function' && value instanceof Blob) {
if (formData === null) {
formData = new FormData();
}
const blobId = nextPartId++;
formData.append(formFieldPrefix + blobId, value);
return serializeBlobID(blobId);
}

const iteratorFn = getIteratorFn(value);
if (iteratorFn) {
const iterator = iteratorFn.call(value);
if (iterator === value) {
// Iterator, not Iterable
// 迭代器,不是可迭代对象
const iteratorId = nextPartId++;
const partJSON = serializeModel(
Array.from(iterator as any),
iteratorId,
);
if (formData === null) {
formData = new FormData();
}
formData.append(formFieldPrefix + iteratorId, partJSON);
return serializeIteratorID(iteratorId);
}
return Array.from(iterator as any);
}

// TODO: ReadableStream is not available in old Node. Remove the typeof check later.
// TODO: ReadableStream 在旧版本 Node 中不可用。之后移除 typeof 检查。
if (
typeof ReadableStream === 'function' &&
value instanceof ReadableStream
) {
return serializeReadableStream(value);
}
const getAsyncIterator: void | (() => $AsyncIterator<any, any, any>) = (
value as any
)[ASYNC_ITERATOR];
if (typeof getAsyncIterator === 'function') {
// We treat AsyncIterables as a Fragment and as such we might need to key them.
// 我们将 AsyncIterables 视为一个片段,因此我们可能需要为它们创建键。
return serializeAsyncIterable(
value as any,
getAsyncIterator.call(value as any),
);
}

// Verify that this is a simple plain object.
// 验证这是一个简单的普通对象。
const proto = getPrototypeOf(value);
if (
proto !== ObjectPrototype &&
(proto === null || getPrototypeOf(proto) !== null)
) {
if (temporaryReferences === undefined) {
throw new Error(
'Only plain objects, and a few built-ins, can be passed to Server Functions. ' +
'Classes or null prototypes are not supported.' +
(__DEV__ ? describeObjectForErrorMessage(parent, key) : ''),
);
}
// We will have written this object to the temporary reference set above
// so we can replace it with a marker to refer to this slot later.
// 我们将会把这个对象写入上面的临时引用集合.这样我们以后就可以用一个标记来替换
// 它,以引用这个槽位。
return serializeTemporaryReferenceMarker();
}
if (__DEV__) {
if ((value as any).$$typeof === REACT_CONTEXT_TYPE) {
console.error(
'React Context Providers cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
);
} else if (objectName(value) !== 'Object') {
console.error(
'Only plain objects can be passed to Server Functions from the Client. ' +
'%s objects are not supported.%s',
objectName(value),
describeObjectForErrorMessage(parent, key),
);
} else if (!isSimpleObject(value)) {
console.error(
'Only plain objects can be passed to Server Functions from the Client. ' +
'Classes or other objects with methods are not supported.%s',
describeObjectForErrorMessage(parent, key),
);
} else if (Object.getOwnPropertySymbols) {
const symbols = Object.getOwnPropertySymbols(value);
if (symbols.length > 0) {
console.error(
'Only plain objects can be passed to Server Functions from the Client. ' +
'Objects with symbol properties like %s are not supported.%s',
symbols[0].description,
describeObjectForErrorMessage(parent, key),
);
}
}
}

return value;
}

if (typeof value === 'string') {
// TODO: Maybe too clever. If we support URL there's no similar trick.
// 待办事项:可能太聪明了。如果我们支持 URL,就没有类似的技巧。
if (value[value.length - 1] === 'Z') {
// Possibly a Date, whose toJSON automatically calls toISOString
// 可能是一个日期,其 toJSON 方法会自动调用 toISOString
const originalValue = parent[key];
if (originalValue instanceof Date) {
return serializeDateFromDateJSON(value);
}
}

return escapeStringValue(value);
}

if (typeof value === 'boolean') {
return value;
}

if (typeof value === 'number') {
return serializeNumber(value);
}

if (typeof value === 'undefined') {
return serializeUndefined();
}

if (typeof value === 'function') {
const referenceClosure = knownServerReferences.get(value);
if (referenceClosure !== undefined) {
const existingReference = writtenObjects.get(value);
if (existingReference !== undefined) {
return existingReference;
}
const { id, bound } = referenceClosure;
const referenceClosureJSON = JSON.stringify(
{ id, bound },
resolveToJSON,
);
if (formData === null) {
// Upgrade to use FormData to allow us to stream this value.
// 升级以使用 FormData,以便我们能够流式传输此值。
formData = new FormData();
}
// The reference to this function came from the same client so we can pass it back.
// 对此函数的引用来自同一个客户端,所以我们可以将其传回。
const refId = nextPartId++;
formData.set(formFieldPrefix + refId, referenceClosureJSON);
const serverReferenceId = serializeServerReferenceID(refId);
// Store the server reference ID for deduplication.
// 存储服务器引用 ID 以进行去重。
writtenObjects.set(value, serverReferenceId);
return serverReferenceId;
}
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
// 待办事项:如果属性名包含冒号,我们不去重。改为转义。
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
// 如果父对象有引用,我们可以通过父对象内的属性名称间接引用该对象。
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
// 存储此对象,以便服务器以后在响应中可以引用它。
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
throw new Error(
'Client Functions cannot be passed directly to Server Functions. ' +
'Only Functions passed from the Server can be passed back again.',
);
}

if (typeof value === 'symbol') {
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.
// 待办事项:如果属性名包含冒号,我们不去重。改为转义。
const parentReference = writtenObjects.get(parent);
if (parentReference !== undefined) {
// If the parent has a reference, we can refer to this object indirectly
// through the property name inside that parent.
// 如果父对象有引用,我们可以通过父对象内的属性名称间接引用该对象。
const reference = parentReference + ':' + key;
// Store this object so that the server can refer to it later in responses.
// 存储此对象,以便服务器可以在以后的响应中引用它。
writeTemporaryReference(temporaryReferences, reference, value);
return serializeTemporaryReferenceMarker();
}
}
throw new Error(
'Symbols cannot be passed to a Server Function without a ' +
'temporary reference set. Pass a TemporaryReferenceSet to the options.' +
(__DEV__ ? describeObjectForErrorMessage(parent, key) : ''),
);
}

if (typeof value === 'bigint') {
return serializeBigInt(value);
}

throw new Error(
`Type ${typeof value} is not supported as an argument to a Server Function.`,
);
}

function serializeModel(model: ReactServerValue, id: number): string {
if (typeof model === 'object' && model !== null) {
const reference = serializeByValueID(id);
writtenObjects.set(model, reference);
if (temporaryReferences !== undefined) {
// Store this object so that the server can refer to it later in responses.
// 存储此对象,以便服务器以后在响应中可以引用它。
writeTemporaryReference(temporaryReferences, reference, model);
}
}
modelRoot = model;
return JSON.stringify(model, resolveToJSON);
}

function abort(reason: mixed): void {
if (pendingParts > 0) {
// 不要以后再解析。
pendingParts = 0; // Don't resolve again later.
// Resolve with what we have so far, which may have holes at this point.
// They'll error when the stream completes on the server.
// 用我们目前所拥有的内容进行解析,这时可能存在漏洞。
// 当服务器上的流完成时,它们会报错。
if (formData === null) {
resolve(json);
} else {
resolve(formData);
}
}
}

const json = serializeModel(root, 0);

if (formData === null) {
// If it's a simple data structure, we just use plain JSON.
// 如果是一个简单的数据结构,我们就使用普通的 JSON。
resolve(json);
} else {
// Otherwise, we use FormData to let us stream in the result.
// 否则,我们使用 FormData 来让我们流式处理结果。
formData.set(formFieldPrefix + '0', json);
if (pendingParts === 0) {
resolve(formData);
}
}

return abort;
}

注册绑定服务器引用

export function registerBoundServerReference<T extends Function>(
reference: T,
id: ServerReferenceId,
bound: null | Thenable<Array<any>>,
encodeFormAction: void | EncodeFormActionCallback,
): void {
if (knownServerReferences.has(reference)) {
return;
}

knownServerReferences.set(reference, {
id,
originalBind: reference.bind,
bound,
});

// Expose encoder for use by SSR, as well as a special bind that can be used to
// keep server capabilities.
// 公开编码器以供 SSR 使用,以及一个可以用于保持服务器功能的特殊绑定。
if (usedWithSSR) {
// Only expose this in builds that would actually use it. Not needed in the browser.
// 仅在实际会使用它的构建中暴露。浏览器中不需要。
const $$FORM_ACTION =
encodeFormAction === undefined
? defaultEncodeFormAction
: function (
// this: any => Promise<any>,
this: (any: any) => Promise<any>,
identifierPrefix: string,
): ReactCustomFormAction {
return customEncodeFormAction(
this,
identifierPrefix,
encodeFormAction,
);
};
Object.defineProperties(reference as any, {
$$FORM_ACTION: { value: $$FORM_ACTION },
$$IS_SIGNATURE_EQUAL: { value: isSignatureEqual },
bind: { value: bind },
});
}
}

注册服务器引用

export function registerServerReference<T: Function>(
reference: T,
id: ServerReferenceId,
encodeFormAction?: EncodeFormActionCallback,
): ServerReference<T> {
registerBoundServerReference(reference, id, null, encodeFormAction);
return reference;
}

创建绑定服务器引用

export function createBoundServerReference<A extends Iterable<any>, T>(
metaData: {
id: ServerReferenceId;
bound: null | Thenable<Array<any>>;
name?: string; // DEV-only
env?: string; // DEV-only
location?: ReactFunctionLocation; // DEV-only
},
callServer: CallServerCallback,
encodeFormAction?: EncodeFormActionCallback,
findSourceMapURL?: FindSourceMapURLCallback, // DEV-only
): (...A) => Promise<T> {
const id = metaData.id;
const bound = metaData.bound;
let action = function (): Promise<T> {
const args = Array.prototype.slice.call(arguments);
const p = bound;
if (!p) {
return callServer(id, args);
}
if (p.status === 'fulfilled') {
const boundArgs = p.value;
return callServer(id, boundArgs.concat(args));
}
// Since this is a fake Promise whose .then doesn't chain, we have to wrap it.
// TODO: Remove the wrapper once that's fixed.
// 由于这是一个假的 Promise,它的 .then 不会链式调用,因此我们必须将其包装起来。
// TODO: 一旦修复,移除这个包装。
return (Promise.resolve(p) as any as Promise<Array<any>>).then(
function (boundArgs) {
return callServer(id, boundArgs.concat(args));
},
);
};
if (__DEV__) {
const location = metaData.location;
if (location) {
const functionName = metaData.name || '';
const [, filename, line, col] = location;
const env = metaData.env || 'Server';
const sourceMap =
findSourceMapURL == null ? null : findSourceMapURL(filename, env);
action = createFakeServerFunction(
functionName,
filename,
sourceMap,
line,
col,
env,
action,
);
}
}
registerBoundServerReference(action, id, bound, encodeFormAction);
return action;
}

创建服务器引用

export function createServerReference<A extends Iterable<any>, T>(
id: ServerReferenceId,
callServer: CallServerCallback,
encodeFormAction?: EncodeFormActionCallback,
findSourceMapURL?: FindSourceMapURLCallback, // DEV-only
functionName?: string,
// ): (...A) => Promise<T> {
): (...a: A) => Promise<T> {
let action = function (): Promise<T> {
const args = Array.prototype.slice.call(arguments);
return callServer(id, args);
};
if (__DEV__) {
// Let's see if we can find a source map for the file which contained the
// server action. We extract it from the runtime so that it's resilient to
// multiple passes of compilation as long as we can find the final source map.
// 让我们看看是否能找到包含服务器操作的文件的源映射。我们从运行时中提取它,这样只要我们能找到最终的源映射,它就能在多次编译过程中保持稳定。
const location = parseStackLocation(new Error('react-stack-top-frame'));
if (location !== null) {
const [, filename, line, col] = location;
// While the environment that the Server Reference points to can be
// in any environment, what matters here is where the compiled source
// is from and that's in the currently executing environment. We hard
// code that as the value "Client" in case the findSourceMapURL helper
// needs it.
// 虽然服务器引用指向的环境可以是任何环境,
// 这里重要的是编译后的源代码来自哪里,那就是当前执行的环境。
// 我们将其硬编码为值 "Client",以防 findSourceMapURL 辅助函数需要它。
const env = 'Client';
const sourceMap =
findSourceMapURL == null ? null : findSourceMapURL(filename, env);
action = createFakeServerFunction(
functionName || '',
filename,
sourceMap,
line,
col,
env,
action,
);
}
}
registerBoundServerReference(action, id, null, encodeFormAction);
return action;
}

常量

对象原型

备注

源码中 40 行

const ObjectPrototype = Object.prototype;

已知服务器引用

备注

源码中 72 - 73 行

const knownServerReferences: WeakMap<Function, ServerReferenceClosure> =
new WeakMap();

__PROTO__

备注

源码中 101 行

const __PROTO__ = '__proto__';

绑定缓存

备注

源码中 922 - 925 行

const boundCache: WeakMap<
ServerReferenceClosure,
Thenable<FormData>
> = new WeakMap();

函数绑定

备注

源码中 1235 - 1238 行

// 函数绑定
const FunctionBind = Function.prototype.bind;
// 数组切片
const ArraySlice = Array.prototype.slice;

v8 帧正则表达式

备注

源码中 1352 - 1361 行

// This matches either of these V8 formats.
// 这匹配以下任一 V8 格式。
// at name (filename:0:0)
// at filename:0:0
// at async filename:0:0
// v8 帧正则表达式
const v8FrameRegExp =
/^ {3} at (?:(.+) \((.+):(\d+):(\d+)\)|(?:async )?(.+):(\d+):(\d+))$/;
// This matches either of these JSC/SpiderMonkey formats.
// 这匹配以下任一 JSC/SpiderMonkey 格式。
// name@filename:0:0
// filename:0:0
// jscSpiderMonkeyFrame 正则表达式
const jscSpiderMonkeyFrameRegExp = /(?:(.*)@)?(.*):(\d+):(\d+)/;

变量

假服务器功能索引

备注

源码中 1093 行

let fakeServerFunctionIdx = 0;

工具

按值 ID 序列化

function serializeByValueID(id: number): string {
return '$' + id.toString(16);
}

按 Promise ID 序列化

function serializePromiseID(id: number): string {
return '$@' + id.toString(16);
}

按引用 ID 序列化

function serializeServerReferenceID(id: number): string {
return '$h' + id.toString(16);
}

序列化临时引用标记

function serializeTemporaryReferenceMarker(): string {
return '$T';
}

系列化表单数据引用

function serializeFormDataReference(id: number): string {
return '$K' + id.toString(16);
}

序列化数字

function serializeNumber(number: number): string | number {
if (Number.isFinite(number)) {
if (number === 0 && 1 / number === -Infinity) {
return '$-0';
} else {
return number;
}
} else {
if (number === Infinity) {
return '$Infinity';
} else if (number === -Infinity) {
return '$-Infinity';
} else {
return '$NaN';
}
}
}

序列化 undefined`

function serializeUndefined(): string {
return '$undefined';
}

从日期 JSON 序列化日期

function serializeDateFromDateJSON(dateJSON: string): string {
// JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
// We need only tack on a $D prefix.
// JSON.stringify 会自动调用 Date.prototype.toJSON,它会调用 toISOString。
// 我们只需要加上 $D 前缀。
return '$D' + dateJSON;
}

序列化大整数

function serializeBigInt(n: bigint): string {
return '$n' + n.toString(10);
}

序列化 Map ID

function serializeMapID(id: number): string {
return '$Q' + id.toString(16);
}

序列化 Set ID

function serializeSetID(id: number): string {
return '$W' + id.toString(16);
}

序列化 Blob ID

function serializeBlobID(id: number): string {
return '$B' + id.toString(16);
}

序列化迭代器 ID

function serializeIteratorID(id: number): string {
return '$i' + id.toString(16);
}

转义字符串值

function escapeStringValue(value: string): string {
if (value[0] === '$') {
// We need to escape $ prefixed strings since we use those to encode
// references to IDs and as special symbol values.
// 我们需要转义以 $ 开头的字符串,因为我们用它们来编码对 ID 的引用以及用作特殊符号值。
return '$' + value;
} else {
return value;
}
}

编码表单数据

function encodeFormData(reference: any): Thenable<FormData> {
let resolve, reject;
// We need to have a handle on the thenable so that we can synchronously set
// its status from processReply, when it can complete synchronously.
// 我们需要掌握 thenable,以便我们可以从 processReply 同步设置它的状态,当它可以
// 同步完成时。
const thenable: Thenable<FormData> = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
processReply(
reference,
'',
// 待办事项:这意味着在渐进增强中无法将 React 元素用作状态。
undefined, // TODO: This means React Elements can't be used as state in progressive enhancement.
(body: string | FormData) => {
if (typeof body === 'string') {
const data = new FormData();
data.append('0', body);
body = data;
}
const fulfilled: FulfilledThenable<FormData> = thenable as any;
fulfilled.status = 'fulfilled';
fulfilled.value = body;
resolve(body);
},
e => {
const rejected: RejectedThenable<FormData> = thenable as any;
rejected.status = 'rejected';
rejected.reason = e;
reject(e);
},
);
return thenable;
}

默认编码表单操作

function defaultEncodeFormAction(
this: (any: any) => Promise<any>,
identifierPrefix: string,
): ReactCustomFormAction {
const referenceClosure = knownServerReferences.get(this);
if (!referenceClosure) {
throw new Error(
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
'This is a bug in React.',
);
}
let data: null | FormData = null;
let name;
const boundPromise = referenceClosure.bound;
if (boundPromise !== null) {
let thenable = boundCache.get(referenceClosure);
if (!thenable) {
const { id, bound } = referenceClosure;
thenable = encodeFormData({ id, bound });
boundCache.set(referenceClosure, thenable);
}
if (thenable.status === 'rejected') {
throw thenable.reason;
} else if (thenable.status !== 'fulfilled') {
throw thenable;
}
const encodedFormData = thenable.value;
// This is hacky but we need the identifier prefix to be added to
// all fields but the suspense cache would break since we might get
// a new identifier each time. So we just append it at the end instead.
// 这很临时,但我们需要将标识符前缀添加到所有字段中,
// 但是 suspense 缓存会出问题,因为我们每次可能都会获得一个新的标识符。
// 所以我们只是将它附加在最后。
const prefixedData = new FormData();
encodedFormData.forEach((value: string | File, key: string) => {
prefixedData.append('$ACTION_' + identifierPrefix + ':' + key, value);
});
data = prefixedData;
// We encode the name of the prefix containing the data.
// 我们对包含数据的前缀名称进行编码。
name = '$ACTION_REF_' + identifierPrefix;
} else {
// This is the simple case so we can just encode the ID.
// 这是简单的情况,所以我们可以直接编码ID。
name = '$ACTION_ID_' + referenceClosure.id;
}
return {
name: name,
method: 'POST',
encType: 'multipart/form-data',
data: data,
};
}

自定义编码表单操作

function customEncodeFormAction(
reference: any => Promise<any>,
identifierPrefix: string,
encodeFormAction: EncodeFormActionCallback,
): ReactCustomFormAction {
const referenceClosure = knownServerReferences.get(reference);
if (!referenceClosure) {
throw new Error(
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
'This is a bug in React.',
);
}
let boundPromise: Promise<Array<any>> = (referenceClosure.bound: any);
if (boundPromise === null) {
boundPromise = Promise.resolve([]);
}
return encodeFormAction(referenceClosure.id, boundPromise);
}

签名是否相等

function isSignatureEqual(
// this: any => Promise<any>,
this: (any: any) => Promise<any>,
referenceId: ServerReferenceId,
numberOfBoundArgs: number,
): boolean {
const referenceClosure = knownServerReferences.get(this);
if (!referenceClosure) {
throw new Error(
'Tried to encode a Server Action from a different instance than the encoder is from. ' +
'This is a bug in React.',
);
}
if (referenceClosure.id !== referenceId) {
// These are different functions.
// 这些是不同的函数。
return false;
}
// Now check if the number of bound arguments is the same.
// 现在检查绑定参数的数量是否相同。
const boundPromise = referenceClosure.bound;
if (boundPromise === null) {
// No bound arguments.
// 没有绑定参数。
return numberOfBoundArgs === 0;
}
// Unwrap the bound arguments array by suspending, if necessary. As with
// encodeFormData, this means isSignatureEqual can only be called while React
// is rendering.
// 如果必要,通过挂起来展开绑定的参数数组。和 encodeFormData 一样,这意味着
// isSignatureEqual 只能在 React 渲染时调用。
switch (boundPromise.status) {
case 'fulfilled': {
const boundArgs = boundPromise.value;
return boundArgs.length === numberOfBoundArgs;
}
case 'pending': {
throw boundPromise;
}
case 'rejected': {
throw boundPromise.reason;
}
default: {
if (typeof boundPromise.status === 'string') {
// Only instrument the thenable if the status if not defined.
// 只有在状态未定义的情况下才对 thenable 进行检测。
} else {
const pendingThenable: PendingThenable<Array<any>> =
boundPromise as any;
pendingThenable.status = 'pending';
pendingThenable.then(
(boundArgs: Array<any>) => {
const fulfilledThenable: FulfilledThenable<Array<any>> =
boundPromise as any;
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = boundArgs;
},
(error: mixed) => {
const rejectedThenable: RejectedThenable<number> =
boundPromise as any;
rejectedThenable.status = 'rejected';
rejectedThenable.reason = error;
},
);
}
throw boundPromise;
}
}
}

创建假服务器

function createFakeServerFunction<A extends Iterable<any>, T>(
name: string,
filename: string,
sourceMap: null | string,
line: number,
col: number,
environmentName: string,
innerFunction: (...A) => Promise<T>,
): (...A) => Promise<T> {
// This creates a fake copy of a Server Module. It represents the Server Action on the server.
// We use an eval so we can source map it to the original location.
// 这会创建一个服务器模块的假副本。它代表服务器上的服务器操作。
// 我们使用 eval,这样可以将其源映射到原始位置。

const comment =
'/* This module is a proxy to a Server Action. Turn on Source Maps to see the server source. */';

if (!name) {
// An eval:ed function with no name gets the name "eval". We give it something more descriptive.
// 一个通过 eval 创建但没有名字的函数会被命名为 "eval"。我们给它一个更具描述性的名字。
name = '<anonymous>';
}
const encodedName = JSON.stringify(name);
// We generate code where both the beginning of the function and its parenthesis is at the line
// and column of the server executed code. We use a method form since that lets us name it
// anything we want and because the beginning of the function and its parenthesis is the same
// column. Because Chrome inspects the location of the parenthesis and Firefox inspects the
// location of the beginning of the function. By not using a function expression we avoid the
// ambiguity.
// 我们生成的代码中,函数的开头和它的括号都位于服务器执行代码的行和列上。
// 我们使用方法形式,因为这样可以任意命名,并且函数的开头和它的括号在同一列上。
// 因为 Chrome 检查的是括号的位置,而 Firefox 检查的是函数开头的位置。
// 通过不使用函数表达式,我们可以避免这种歧义。
let code;
if (line <= 1) {
const minSize = encodedName.length + 7;
code =
's=>({' +
encodedName +
' '.repeat(col < minSize ? 0 : col - minSize) +
':' +
'(...args) => s(...args)' +
'})\n' +
comment;
} else {
code =
comment +
'\n'.repeat(line - 2) +
'server=>({' +
encodedName +
':\n' +
' '.repeat(col < 1 ? 0 : col - 1) +
// The function body can get printed so we make it look nice.
// This "calls the server with the arguments".
// 函数体可以被打印出来,所以我们让它看起来漂亮。
// 这“使用参数调用服务器”。
'(...args) => server(...args)' +
'})';
}

if (filename.startsWith('/')) {
// If the filename starts with `/` we assume that it is a file system file
// rather than relative to the current host. Since on the server fully qualified
// stack traces use the file path.
// TODO: What does this look like on Windows?
// 如果文件名以 `/` 开头,我们假设它是一个文件系统文件
// 而不是相对于当前主机的文件。因为在服务器上,完全限定的
// 堆栈跟踪使用文件路径。
// 待办事项:在 Windows 上这会是什么样子?
filename = 'file://' + filename;
}

if (sourceMap) {
// We use the prefix about://React/ to separate these from other files listed in
// the Chrome DevTools. We need a "host name" and not just a protocol because
// otherwise the group name becomes the root folder. Ideally we don't want to
// show these at all but there's two reasons to assign a fake URL.
// 1) A printed stack trace string needs a unique URL to be able to source map it.
// 2) If source maps are disabled or fails, you should at least be able to tell
// which file it was.
// 我们使用前缀 about://React/ 来将这些文件与 Chrome 开发者工具中列出的其他文
// 件区分开。我们需要一个“主机名”,而不仅仅是协议,否则组名会变成根文件夹。理想情况
// 下我们不想显示这些,但有两个原因需要分配一个假 URL。
// 1) 打印的堆栈跟踪字符串需要一个唯一的 URL 才能进行源映射。
// 2) 如果源映射被禁用或失败,你至少应该能够判断出它是哪个文件。
code +=
'\n//# sourceURL=about://React/' +
encodeURIComponent(environmentName) +
'/' +
encodeURI(filename) +
// 我们在这里添加一个额外的 s,以便与伪栈帧区分开
'?s' + // We add an extra s here to distinguish from the fake stack frames
fakeServerFunctionIdx++;
code += '\n//# sourceMappingURL=' + sourceMap;
} else if (filename) {
code += '\n//# sourceURL=' + filename;
}

try {
// Eval a factory and then call it to create a closure over the inner function.
// 评估一个工厂函数,然后调用它以创建对内部函数的闭包。
return (0, eval)(code)(innerFunction)[name];
} catch (x) {
// If eval fails, such as if in an environment that doesn't support it,
// we fallback to just returning the inner function.
// 如果 eval 失败,例如在不支持 eval 的环境中,我们会回退到仅返回内部函数。
return innerFunction;
}
}

绑定

function bind(this: Function): Function {
const referenceClosure = knownServerReferences.get(this);

if (!referenceClosure) {
return FunctionBind.apply(this, arguments);
}

const newFn = referenceClosure.originalBind.apply(this, arguments);

if (__DEV__) {
const thisBind = arguments[0];
if (thisBind != null) {
// This doesn't warn in browser environments since it's not instrumented outside
// usedWithSSR. This makes this an SSR only warning which we don't generally do.
// 由于在浏览器环境中没有进行监控,因此不会发出警告
// usedWithSSR。这使其成为仅限 SSR 的警告,而我们通常不会这样做。
// TODO: Consider a DEV only instrumentation in the browser.
// 待办事项:考虑在浏览器中仅进行开发用途的检测。
console.error(
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
);
}
}

const args = ArraySlice.call(arguments, 1);
let boundPromise = null;
if (referenceClosure.bound !== null) {
boundPromise = Promise.resolve(referenceClosure.bound as any).then(
boundArgs => boundArgs.concat(args),
);
} else {
boundPromise = Promise.resolve(args);
}

knownServerReferences.set(newFn, {
id: referenceClosure.id,
originalBind: newFn.bind,
bound: boundPromise,
});

// Expose encoder for use by SSR, as well as a special bind that can be used to
// keep server capabilities.
// 公开编码器以供 SSR 使用,以及一个可以用于保持服务器功能的特殊绑定。
if (usedWithSSR) {
// Only expose this in builds that would actually use it. Not needed on the client.
// 仅在实际会使用它的构建中暴露。客户端不需要。
Object.defineProperties(newFn as any, {
$$FORM_ACTION: { value: this.$$FORM_ACTION },
$$IS_SIGNATURE_EQUAL: { value: isSignatureEqual },
bind: { value: bind },
});
}

return newFn;
}

解析堆栈位置

function parseStackLocation(error: Error): null | ReactFunctionLocation {
// This parsing is special in that we know that the calling function will always
// be a module that initializes the server action. We also need this part to work
// cross-browser so not worth a Config. It's DEV only so not super code size
// sensitive but also a non-essential feature.
// 这个解析是特殊的,因为我们知道调用函数总是一个初始化服务器操作的模块。我们还需要这
// 一部分能跨浏览器工作,所以不值得使用配置。这只是开发用的,所以代码大小不是特别敏
// 感,但也是一个非核心功能。
let stack = error.stack;
if (stack.startsWith('Error: react-stack-top-frame\n')) {
// V8's default formatting prefixes with the error message which we
// don't want/need.
// V8 的默认格式在错误信息前加前缀,而我们不想要/不需要这个。
stack = stack.slice(29);
}
const endOfFirst = stack.indexOf('\n');
let secondFrame;
if (endOfFirst !== -1) {
// Skip the first frame.
// 跳过第一帧。
const endOfSecond = stack.indexOf('\n', endOfFirst + 1);
if (endOfSecond === -1) {
secondFrame = stack.slice(endOfFirst + 1);
} else {
secondFrame = stack.slice(endOfFirst + 1, endOfSecond);
}
} else {
secondFrame = stack;
}

let parsed = v8FrameRegExp.exec(secondFrame);
if (!parsed) {
parsed = jscSpiderMonkeyFrameRegExp.exec(secondFrame);
if (!parsed) {
return null;
}
}

let name = parsed[1] || '';
if (name === '<anonymous>') {
name = '';
}
let filename = parsed[2] || parsed[5] || '';
if (filename === '<anonymous>') {
filename = '';
}
// This is really the enclosingLine/Column.
// 这实际上是封闭的行/列。
const line = +(parsed[3] || parsed[6]);
const col = +(parsed[4] || parsed[7]);

return [name, filename, line, col];
}

类型

React JSON 值

type ReactJSONValue =
| string
| boolean
| number
| null
| $ReadOnlyArray<ReactJSONValue>
| ReactServerObject;

服务器引用闭包

type ServerReferenceClosure = {
id: ServerReferenceId;
originalBind: Function;
bound: null | Thenable<Array<any>>;
};

React 服务器对象

type ReactServerObject = {+[key: string]: ReactServerValue};