React 子 Fiber
一、作用
二、导出的常量
1. 协调子 Fiber
export const reconcileChildFibers: ChildReconciler =
createChildReconciler(true);
2. 挂载子 Fiber
export const mountChildFibers: ChildReconciler = createChildReconciler(false);
三、在回溯时重置子协调器
export function resetChildReconcilerOnUnwind(): void {
// On unwind, clear any pending thenables that were used.
// 在展开时,清除所有未处理的 thenable 对象。
thenableState = null;
thenableIndexCounter = 0;
}
四、克隆子 Fiber
备注
createWorkInProgress()由 ReactFiber 提供
export function cloneChildFibers(
current: Fiber | null,
workInProgress: Fiber,
): void {
if (current !== null && workInProgress.child !== current.child) {
throw new Error('Resuming work not yet implemented.');
}
if (workInProgress.child === null) {
return;
}
let currentChild = workInProgress.child;
let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
workInProgress.child = newChild;
newChild.return = workInProgress;
while (currentChild.sibling !== null) {
currentChild = currentChild.sibling;
newChild = newChild.sibling = createWorkInProgress(
currentChild,
currentChild.pendingProps,
);
newChild.return = workInProgress;
}
newChild.sibling = null;
}
五、重置子 Fiber
备注
resetWorkInProgress()由 ReactFiber 提供
// Reset a workInProgress child set to prepare it for a second pass.
// 重置一个正在进行的子集,以为第二次处理做准备。
export function resetChildFibers(workInProgress: Fiber, lanes: Lanes): void {
let child = workInProgress.child;
while (child !== null) {
resetWorkInProgress(child, lanes);
child = child.sibling;
}
}
六、验证挂起列表子项
export function validateSuspenseListChildren(
children: mixed,
revealOrder: SuspenseListRevealOrder,
) {
if (__DEV__) {
if (
(revealOrder == null ||
revealOrder === 'forwards' ||
revealOrder === 'backwards' ||
revealOrder === 'unstable_legacy-backwards') &&
children !== undefined &&
children !== null &&
children !== false
) {
if (isArray(children)) {
for (let i = 0; i < children.length; i++) {
if (!validateSuspenseListNestedChild(children[i], i)) {
return;
}
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
const childrenIterator = iteratorFn.call(children);
if (childrenIterator) {
let step = childrenIterator.next();
let i = 0;
for (; !step.done; step = childrenIterator.next()) {
if (!validateSuspenseListNestedChild(step.value, i)) {
return;
}
i++;
}
}
} else if (
enableAsyncIterableChildren &&
typeof (children as any)[ASYNC_ITERATOR] === 'function'
) {
// TODO: Technically we should warn for nested arrays inside the
// async iterable but it would require unwrapping the array.
// However, this mistake is not as easy to make so it's ok not to warn.
//
// TODO:从技术上讲,我们应该对异步可迭代对象中的嵌套数组发出警告,但这需要展开数组。
// 不过,这种错误不容易发生,所以不发出警告也可以。
} else if (
enableAsyncIterableChildren &&
children.$$typeof === REACT_ELEMENT_TYPE &&
typeof children.type === 'function' &&
(Object.prototype.toString.call(children.type) ===
'[object GeneratorFunction]' ||
Object.prototype.toString.call(children.type) ===
'[object AsyncGeneratorFunction]')
) {
console.error(
'A generator Component was passed to a <SuspenseList revealOrder="%s" />. ' +
'This is not supported as a way to generate lists. Instead, pass an ' +
'iterable as the children.',
revealOrder,
);
} else {
console.error(
'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
'This is not useful since it needs multiple rows. ' +
'Did you mean to pass multiple children or an array?',
revealOrder,
);
}
}
}
}
}
七、变量
1. 状态
备注
源码中 78 - 83 行
// This tracks the thenables that are unwrapped during reconcilation.
// 这跟踪在协调过程中被解包的 thenables。
// 可则状态
let thenableState: ThenableState | null = null;
// thenable索引计数器
let thenableIndexCounter: number = 0;
// Server Components Meta Data
// 服务器组件元数据
// 当前调试信息
let currentDebugInfo: null | ReactDebugInfo = null;
2. en
备注
在源码中的 124 - 225 行 (包含在测试环境下的初始化)
// 已警告有关地图
let didWarnAboutMaps;
// 已警告有关生成器
let didWarnAboutGenerators;
// 所有者拥有钥匙使用警告
let ownerHasKeyUseWarning;
// 所有者具有功能类型警告
let ownerHasFunctionTypeWarning;
// 所有者具有符号类型警告
let ownerHasSymbolTypeWarning;
// 警告缺失键
let warnForMissingKey = (
returnFiber: Fiber,
workInProgress: Fiber,
child: mixed,
) => {};
if (__DEV__) {
didWarnAboutMaps = false;
didWarnAboutGenerators = false;
/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
* updates.
*
* 如果动态数组的子元素没有显式设置 key,或对象 key 无效,则发出警告。
* 这让我们能够在更新之间跟踪子元素。
*/
ownerHasKeyUseWarning = {} as { [string]: boolean };
ownerHasFunctionTypeWarning = {} as { [string]: boolean };
ownerHasSymbolTypeWarning = {} as { [string]: boolean };
warnForMissingKey = (
returnFiber: Fiber,
workInProgress: Fiber,
child: mixed,
) => {
if (child === null || typeof child !== 'object') {
return;
}
if (
!child._store ||
((child._store.validated || child.key != null) &&
child._store.validated !== 2)
) {
return;
}
if (typeof child._store !== 'object') {
throw new Error(
'React Component in warnForMissingKey should have a _store. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
child._store.validated = 1;
const componentName = getComponentNameFromFiber(returnFiber);
const componentKey = componentName || 'null';
if (ownerHasKeyUseWarning[componentKey]) {
return;
}
ownerHasKeyUseWarning[componentKey] = true;
const childOwner = child._owner;
const parentOwner = returnFiber._debugOwner;
let currentComponentErrorInfo = '';
if (parentOwner && typeof parentOwner.tag === 'number') {
const name = getComponentNameFromFiber(parentOwner as any);
if (name) {
currentComponentErrorInfo =
'\n\nCheck the render method of `' + name + '`.';
}
}
if (!currentComponentErrorInfo) {
if (componentName) {
currentComponentErrorInfo = `\n\nCheck the top-level render call using <${componentName}>.`;
}
}
// Usually the current owner is the offender, but if it accepts children as a
// property, it may be the creator of the child that's responsible for
// assigning it a key.
//
// 通常,当前所有者是责任人,但如果它把孩子视为一种财产,那么可能是孩子的创造者负责为其分配一个钥匙。
let childOwnerAppendix = '';
if (childOwner != null && parentOwner !== childOwner) {
let ownerName = null;
if (typeof childOwner.tag === 'number') {
ownerName = getComponentNameFromFiber(childOwner as any);
} else if (typeof childOwner.name === 'string') {
ownerName = childOwner.name;
}
if (ownerName) {
// Give the component that originally created this child.
// 提供最初创建该子组件的组件。
childOwnerAppendix = ` It was passed a child from ${ownerName}.`;
}
}
runWithFiberInDEV(workInProgress, () => {
console.error(
'Each child in a list should have a unique "key" prop.' +
'%s%s See https://react.dev/link/warning-keys for more information.',
currentComponentErrorInfo,
childOwnerAppendix,
);
});
};
}
八、工具
1. 推送调试信息
function pushDebugInfo(
debugInfo: null | ReactDebugInfo,
): null | ReactDebugInfo {
if (!__DEV__) {
return null;
}
const previousDebugInfo = currentDebugInfo;
if (debugInfo == null) {
// Leave inplace
// 保留原位
} else if (previousDebugInfo === null) {
currentDebugInfo = debugInfo;
} else {
// If we have two debugInfo, we need to create a new one. This makes the array no longer
// live so we'll miss any future updates if we received more so ideally we should always
// do this after both have fully resolved/unsuspended.
//
// 如果我们有两个 debugInfo,我们需要创建一个新的。这会导致数组不再是活动的,因此如果我们接收
// 更多信息时将错过任何未来的更新,所以理想情况下我们应该在两者都完全解析/解除暂停后再进行此操作。
currentDebugInfo = previousDebugInfo.concat(debugInfo);
}
return previousDebugInfo;
}
2. 获取当前调试任务
function getCurrentDebugTask(): null | ConsoleTask {
// Get the debug task of the parent Server Component if there is one.
// 如果存在父服务器组件,则获取其调试任务。
if (__DEV__) {
const debugInfo = currentDebugInfo;
if (debugInfo != null) {
for (let i = debugInfo.length - 1; i >= 0; i--) {
if (debugInfo[i].name != null) {
const componentInfo: ReactComponentInfo = debugInfo[i];
const debugTask: ?ConsoleTask = componentInfo.debugTask;
if (debugTask != null) {
return debugTask;
}
}
}
}
}
return null;
}
3. 验证片段属性
备注
runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现
// Given a fragment, validate that it can only be provided with fragment props
// We do this here instead of BeginWork because the Fragment fiber doesn't have
// the whole props object, only the children and is shared with arrays.
//
// 给定一个片段,验证它只能与片段属性一起提供我们在这里执行此操作而不是在 BeginWork 中
// 执行,因为 Fragment fiber 没有完整的 props 对象,只有 children,并且与数组共享。
function validateFragmentProps(
element: ReactElement,
fiber: null | Fiber,
returnFiber: Fiber,
) {
if (__DEV__) {
const keys = Object.keys(element.props);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (
key !== 'children' &&
key !== 'key' &&
(enableFragmentRefs ? key !== 'ref' : true)
) {
if (fiber === null) {
// For unkeyed root fragments without refs (enableFragmentRefs),
// there's no Fiber. We create a fake one just for error stack handling.
//
// 对于没有引用的未键控根片段(enableFragmentRefs),
// 没有 Fiber。我们创建一个假的 Fiber 仅用于错误堆栈处理。
fiber = createFiberFromElement(element, returnFiber.mode, 0);
if (__DEV__) {
fiber._debugInfo = currentDebugInfo;
}
fiber.return = returnFiber;
}
runWithFiberInDEV(
fiber,
erroredKey => {
if (enableFragmentRefs) {
console.error(
'Invalid prop `%s` supplied to `React.Fragment`. ' +
'React.Fragment can only have `key`, `ref`, and `children` props.',
erroredKey,
);
} else {
console.error(
'Invalid prop `%s` supplied to `React.Fragment`. ' +
'React.Fragment can only have `key` and `children` props.',
erroredKey,
);
}
},
key,
);
break;
}
}
}
}
4. 展开可处理对象
备注
createThenableState()由 ReactFiberThenable#createThenableState 实现trackUsedThenable()由 ReactFiberThenable#trackUsedThenable 实现
function unwrapThenable<T>(thenable: Thenable<T>): T {
const index = thenableIndexCounter;
thenableIndexCounter += 1;
if (thenableState === null) {
thenableState = createThenableState();
}
return trackUsedThenable(thenableState, thenable, index);
}
5. 强制引用
function coerceRef(workInProgress: Fiber, element: ReactElement): void {
// TODO: This is a temporary, intermediate step. Now that enableRefAsProp is on,
// we should resolve the `ref` prop during the begin phase of the component
// it's attached to (HostComponent, ClassComponent, etc).
//
// 待办事项:这是一个临时的中间步骤。现在 enableRefAsProp 已启用,
// 我们应该在组件的开始阶段解析 `ref` 属性
// 该组件可以是 HostComponent、ClassComponent 等。
const refProp = element.props.ref;
// TODO: With enableRefAsProp now rolled out, we shouldn't use the `ref` field. We
// should always read the ref from the prop.
//
// TODO: 随着 enableRefAsProp 的推出,我们不应该再使用 `ref` 字段。我们
// 应始终从 prop 中读取 ref。
workInProgress.ref = refProp !== undefined ? refProp : null;
}
6. 抛出无效对象类型(实现)
function throwOnInvalidObjectTypeImpl(returnFiber: Fiber, newChild: Object) {
if (newChild.$$typeof === REACT_LEGACY_ELEMENT_TYPE) {
throw new Error(
'A React Element from an older version of React was rendered. ' +
'This is not supported. It can happen if:\n' +
'- Multiple copies of the "react" package is used.\n' +
'- A library pre-bundled an old copy of "react" or "react/jsx-runtime".\n' +
'- A compiler tries to "inline" JSX instead of using the runtime.',
);
}
const childString = Object.prototype.toString.call(newChild);
throw new Error(
`Objects are not valid as a React child (found: ${
childString === '[object Object]'
? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
: childString
}). ` +
'If you meant to render a collection of children, use an array ' +
'instead.',
);
}
7. 抛出无效对象类型
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
const debugTask = getCurrentDebugTask();
if (__DEV__ && debugTask !== null) {
debugTask.run(
throwOnInvalidObjectTypeImpl.bind(null, returnFiber, newChild),
);
} else {
throwOnInvalidObjectTypeImpl(returnFiber, newChild);
}
}
8. 警告函数类型(实现)
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function warnOnFunctionTypeImpl(returnFiber: Fiber, invalidChild: Function) {
if (__DEV__) {
const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
if (ownerHasFunctionTypeWarning[parentName]) {
return;
}
ownerHasFunctionTypeWarning[parentName] = true;
const name = invalidChild.displayName || invalidChild.name || 'Component';
if (returnFiber.tag === HostRoot) {
console.error(
'Functions are not valid as a React child. This may happen if ' +
'you return %s instead of <%s /> from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
' root.render(%s)',
name,
name,
name,
);
} else {
console.error(
'Functions are not valid as a React child. This may happen if ' +
'you return %s instead of <%s /> from render. ' +
'Or maybe you meant to call this function rather than return it.\n' +
' <%s>{%s}</%s>',
name,
name,
parentName,
name,
parentName,
);
}
}
}
9. 警告函数类型
function warnOnFunctionType(returnFiber: Fiber, invalidChild: Function) {
const debugTask = getCurrentDebugTask();
if (__DEV__ && debugTask !== null) {
debugTask.run(warnOnFunctionTypeImpl.bind(null, returnFiber, invalidChild));
} else {
warnOnFunctionTypeImpl(returnFiber, invalidChild);
}
}
10. 警告符号类型(实现)
备注
getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function warnOnSymbolTypeImpl(returnFiber: Fiber, invalidChild: symbol) {
if (__DEV__) {
const parentName = getComponentNameFromFiber(returnFiber) || 'Component';
if (ownerHasSymbolTypeWarning[parentName]) {
return;
}
ownerHasSymbolTypeWarning[parentName] = true;
const name = String(invalidChild);
if (returnFiber.tag === HostRoot) {
console.error(
'Symbols are not valid as a React child.\n' + ' root.render(%s)',
name,
);
} else {
console.error(
'Symbols are not valid as a React child.\n' + ' <%s>%s</%s>',
parentName,
name,
parentName,
);
}
}
}
11. 警告符号类型
function warnOnSymbolType(returnFiber: Fiber, invalidChild: symbol) {
const debugTask = getCurrentDebugTask();
if (__DEV__ && debugTask !== null) {
debugTask.run(warnOnSymbolTypeImpl.bind(null, returnFiber, invalidChild));
} else {
warnOnSymbolTypeImpl(returnFiber, invalidChild);
}
}
12. 创建子协调器
备注
mountChildFibers()由 ReactFiber 提供createWorkInProgress()由 ReactFiber 提供createFiberFromText()由 ReactFiber 提供createFiberFromFragment()由 ReactFiber 提供createFiberFromElement()由 ReactFiber 提供createFiberFromPortal()由 ReactFiber 提供isCompatibleFamilyForHotReloading()由 ReactFiberHotReloading 提供resolveLazy()由 ReactFiberThenable#resolveLazy 实现getIteratorFn()由 shared 提供readContextDuringReconciliation()由 ReactFiberNewContext#readContextDuringReconciliation 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现pushTreeFork()由 ReactFiverTreeContext#pushTreeFork 实现
源码,约 1799 行
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
// live outside of this function.
//
// 这个包装函数存在的原因是我希望在每条路径中克隆代码
// 以便能够通过提前分支来单独优化每条路径。这需要
// 编译器,或者我们可以手动完成。不需要这种分支的辅助函数
// 则放在这个函数外部。
function createChildReconciler(
shouldTrackSideEffects: boolean,
): ChildReconciler {
// 删除子节点
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
// 无操作。
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
// 删除剩余子项
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {
if (!shouldTrackSideEffects) {
// Noop.
// 无操作。
return null;
}
// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
//
// TODO: 对于 shouldClone 的情况,可以通过假设在添加第一个子元素后已经添加了
// 所有内容来进行微优化。
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
// 映射剩余子项
function mapRemainingChildren(
currentFirstChild: Fiber,
): Map<string | number | ReactOptimisticKey, Fiber> {
// Add the remaining children to a temporary map so that we can find them by
// keys quickly. Implicit (null) keys get added to this set with their index
// instead.
//
// 将剩余的子项添加到临时映射中,以便我们可以通过键快速查找它们。隐式(null)键则会
// 使用它们的索引添加到此集合中。
const existingChildren: Map<
| string
| number
// This type is only here for the case when enableOptimisticKey is disabled.
// Remove it after it ships.
// 这个类型仅在 disableOptimisticKey 被禁用的情况下使用。发布后可以删除它。
| ReactOptimisticKey,
Fiber
> = new Map();
let existingChild: null | Fiber = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key === null) {
existingChildren.set(existingChild.index, existingChild);
} else if (
enableOptimisticKey &&
existingChild.key === REACT_OPTIMISTIC_KEY
) {
// For optimistic keys, we store the negative index (minus one) to differentiate
// them from the regular indices. We'll look this up regardless of what the new
// key is, if there's no other match.
//
// 对于乐观键,我们存储负索引(减一)以与普通索引区分。无论新键是什么,我们都会查找它,如果没有
// 其他匹配的话。
existingChildren.set(-existingChild.index - 1, existingChild);
} else {
existingChildren.set(existingChild.key, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
// 使用Fiber
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
//
// 我们目前在这里将 sibling 设置为 null,index 设置为 0,因为这样做比较容易
// 忘记在返回之前执行。例如,对于只有一个子节点的情况。
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
// 放置子元素
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// During hydration, the useId algorithm needs to know which fibers are
// part of a list of children (arrays, iterators).
//
// 在水合过程中,useId 算法需要知道哪些 fiber 是子节点列表(数组、迭代器)的一部分。
newFiber.flags |= Forked;
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
// 这是一次移动。
newFiber.flags |= Placement | PlacementDEV;
return lastPlacedIndex;
} else {
// This item can stay in place.
// 这个物品可以保持原位。
return oldIndex;
}
} else {
// This is an insertion.
// 这是一个插入。
newFiber.flags |= Placement | PlacementDEV;
return lastPlacedIndex;
}
}
// 放置单个子元素
function placeSingleChild(newFiber: Fiber): Fiber {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
//
// 这对只有一个子节点的情况更简单。我们只需要进行一次新的子节点插入位置的处理。
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement | PlacementDEV;
}
return newFiber;
}
// 更新文本节点
function updateTextNode(
returnFiber: Fiber,
current: Fiber | null,
textContent: string,
lanes: Lanes,
) {
if (current === null || current.tag !== HostText) {
// Insert
// 插入
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
created._debugInfo = currentDebugInfo;
}
return created;
} else {
// Update
// 更新
const existing = useFiber(current, textContent);
existing.return = returnFiber;
if (__DEV__) {
existing._debugInfo = currentDebugInfo;
}
return existing;
}
}
// 更新元素
function updateElement(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
const updated = updateFragment(
returnFiber,
current,
element.props.children,
lanes,
element.key,
);
if (enableFragmentRefs) {
coerceRef(updated, element);
}
validateFragmentProps(element, updated, returnFiber);
return updated;
}
if (current !== null) {
if (
current.elementType === elementType ||
// Keep this check inline so it only runs on the false path:
// 保持此检查为内联,以便它只在 false 路径上运行:
(__DEV__
? isCompatibleFamilyForHotReloading(current, element)
: false) ||
// Lazy types should reconcile their resolved type.
// We need to do this after the Hot Reloading check above,
// because hot reloading has different semantics than prod because
// it doesn't resuspend. So we can't let the call below suspend.
// 懒类型应该协调它们解析出的类型。
// 我们需要在上面的热重载检查之后执行此操作,
// 因为热重载的语义不同于生产环境,
// 它不会重新挂起。所以我们不能让下面的调用挂起。
(typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === current.type)
) {
// Move based on index
// 根据索引移动
const existing = useFiber(current, element.props);
coerceRef(existing, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugOwner = element._owner;
existing._debugInfo = currentDebugInfo;
}
return existing;
}
}
// Insert
// 插入
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(created, element);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = currentDebugInfo;
}
return created;
}
// 更新门户
function updatePortal(
returnFiber: Fiber,
current: Fiber | null,
portal: ReactPortal,
lanes: Lanes,
): Fiber {
if (
current === null ||
current.tag !== HostPortal ||
current.stateNode.containerInfo !== portal.containerInfo ||
current.stateNode.implementation !== portal.implementation
) {
// Insert
// 插入
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = currentDebugInfo;
}
return created;
} else {
// Update
// 更新
const existing = useFiber(current, portal.children || []);
if (enableOptimisticKey) {
// If the old key was optimistic we need to now save the real one.
// 如果旧的 key 是乐观的,我们现在需要保存真实的 key。
existing.key = portal.key;
}
existing.return = returnFiber;
if (__DEV__) {
existing._debugInfo = currentDebugInfo;
}
return existing;
}
}
// 更新片段
function updateFragment(
returnFiber: Fiber,
current: Fiber | null,
fragment: Iterable<React$Node>,
lanes: Lanes,
key: ReactKey,
): Fiber {
if (current === null || current.tag !== Fragment) {
// Insert
// 插入(额,好邪恶的词汇)
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
lanes,
key,
);
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
created._debugInfo = currentDebugInfo;
}
return created;
} else {
// Update
// 更新
const existing = useFiber(current, fragment);
if (enableOptimisticKey) {
// If the old key was optimistic we need to now save the real one.
// 如果旧的 key 是乐观的,我们现在需要保存真实的 key。
existing.key = key;
}
existing.return = returnFiber;
if (__DEV__) {
existing._debugInfo = currentDebugInfo;
}
return existing;
}
}
// 创建子项
function createChild(
returnFiber: Fiber,
newChild: any,
lanes: Lanes,
): Fiber | null {
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
//
// 文本节点没有键。如果前一个节点是隐式键控的,即使它不是文本节点,我们也
// 可以继续替换它,而不会中止。
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
lanes,
);
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
created._debugInfo = currentDebugInfo;
}
return created;
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
lanes,
);
coerceRef(created, newChild);
created.return = returnFiber;
if (__DEV__) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
created._debugInfo = currentDebugInfo;
currentDebugInfo = prevDebugInfo;
}
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
lanes,
);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = currentDebugInfo;
}
return created;
}
case REACT_LAZY_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const resolvedChild = resolveLazy(newChild as any);
const created = createChild(returnFiber, resolvedChild, lanes);
currentDebugInfo = prevDebugInfo;
return created;
}
}
if (
isArray(newChild) ||
getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')
) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
lanes,
null,
);
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
created._debugInfo = currentDebugInfo;
currentDebugInfo = prevDebugInfo;
}
return created;
}
// Usable node types
// 可用的节点类型
//
// Unwrap the inner value and recursively call this function again.
// 解包内部值并递归地再次调用此函数。
if (typeof newChild.then === 'function') {
const thenable: Thenable<any> = newChild as any;
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const created = createChild(
returnFiber,
unwrapThenable(thenable),
lanes,
);
currentDebugInfo = prevDebugInfo;
return created;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = newChild as any;
return createChild(
returnFiber,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType(returnFiber, newChild);
}
if (typeof newChild === 'symbol') {
warnOnSymbolType(returnFiber, newChild);
}
}
return null;
}
// 更新插槽
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// Update the fiber if the keys match, otherwise return null.
// 如果键匹配,则更新 fiber,否则返回 null。
const key = oldFiber !== null ? oldFiber.key : null;
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
//
// 文本节点没有键。如果前一个节点是隐式键控的,即使它不是文本节点,我们也可以
// 继续替换它而不必中止。
if (key !== null) {
return null;
}
return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (
newChild.key === key
// If the old child was an optimisticKey, then we'd normally consider that a match,
// but instead, we'll bail to return null from the slot which will bail to slow path.
// That's to ensure that if the new key has a match elsewhere in the list, then that
// takes precedence over assuming the identity of an optimistic slot.
//
// 如果旧的子项是一个乐观键,我们通常会认为这是一个匹配,但相反,我们会直接返回槽的 null,这会转向慢路径。
// 这样做是为了确保如果新键在列表的其他位置有匹配,那将优先于假设一个乐观槽的身份。
) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const updated = updateElement(
returnFiber,
oldFiber,
newChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
} else {
return null;
}
}
case REACT_PORTAL_TYPE: {
if (
newChild.key === key
// If the old child was an optimisticKey, then we'd normally consider that a match,
// but instead, we'll bail to return null from the slot which will bail to slow path.
// That's to ensure that if the new key has a match elsewhere in the list, then that
// takes precedence over assuming the identity of an optimistic slot.
//
// 如果旧的子项是一个乐观键,我们通常会认为这是一个匹配,但相反,我们会直接返回槽的 null,这会转向慢路径。
// 这样做是为了确保如果新键在列表的其他位置有匹配,那将优先于假设一个乐观槽的身份。
) {
return updatePortal(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
}
}
case REACT_LAZY_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const resolvedChild = resolveLazy(newChild as any);
const updated = updateSlot(
returnFiber,
oldFiber,
resolvedChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
}
if (
isArray(newChild) ||
getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')
) {
if (key !== null) {
return null;
}
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const updated = updateFragment(
returnFiber,
oldFiber,
newChild,
lanes,
null,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
// Usable node types
// 可用的节点类型
//
// Unwrap the inner value and recursively call this function again.
// 解包内部值并递归地再次调用此函数。
if (typeof newChild.then === 'function') {
const thenable: Thenable<any> = newChild as any;
const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);
const updated = updateSlot(
returnFiber,
oldFiber,
unwrapThenable(thenable),
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = newChild as any;
return updateSlot(
returnFiber,
oldFiber,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType(returnFiber, newChild);
}
if (typeof newChild === 'symbol') {
warnOnSymbolType(returnFiber, newChild);
}
}
return null;
}
// 从地图更新
function updateFromMap(
existingChildren: Map<string | number | ReactOptimisticKey, Fiber>,
returnFiber: Fiber,
newIdx: number,
newChild: any,
lanes: Lanes,
): Fiber | null {
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
// Text nodes don't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
//
// 文本节点没有键,因此我们既不需要检查旧节点也不需要检查新节点的键。
// 如果两者都是文本节点,则它们匹配。
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(
returnFiber,
matchedFiber,
'' + newChild,
lanes,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) ||
(enableOptimisticKey &&
// If the existing child was an optimistic key, we may still match on the index.
// 如果现有的子节点是一个乐观键,我们仍然可以在索引上匹配。
existingChildren.get(-newIdx - 1)) ||
null;
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const updated = updateElement(
returnFiber,
matchedFiber,
newChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updatePortal(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_LAZY_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const resolvedChild = resolveLazy(newChild as any);
const updated = updateFromMap(
existingChildren,
returnFiber,
newIdx,
resolvedChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
}
if (
isArray(newChild) ||
getIteratorFn(newChild) ||
(enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function')
) {
const matchedFiber = existingChildren.get(newIdx) || null;
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const updated = updateFragment(
returnFiber,
matchedFiber,
newChild,
lanes,
null,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
// Usable node types
// 可用的节点类型
//
// Unwrap the inner value and recursively call this function again.
// 解开内部值并递归地再次调用此函数。
if (typeof newChild.then === 'function') {
const thenable: Thenable<any> = newChild as any;
const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);
const updated = updateFromMap(
existingChildren,
returnFiber,
newIdx,
unwrapThenable(thenable),
lanes,
);
currentDebugInfo = prevDebugInfo;
return updated;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = newChild as any;
return updateFromMap(
existingChildren,
returnFiber,
newIdx,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType(returnFiber, newChild);
}
if (typeof newChild === 'symbol') {
warnOnSymbolType(returnFiber, newChild);
}
}
return null;
}
/**
* Warns if there is a duplicate or missing key
* * 如果存在重复或缺失的键会发出警告
*/
// 警告无效键
function warnOnInvalidKey(
returnFiber: Fiber,
workInProgress: Fiber,
child: mixed,
knownKeys: Set<string> | null,
): Set<string> | null {
if (__DEV__) {
if (typeof child !== 'object' || child === null) {
return knownKeys;
}
switch (child.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
warnForMissingKey(returnFiber, workInProgress, child);
const key = child.key;
if (typeof key !== 'string') {
break;
}
if (knownKeys === null) {
knownKeys = new Set();
knownKeys.add(key);
break;
}
if (!knownKeys.has(key)) {
knownKeys.add(key);
break;
}
runWithFiberInDEV(workInProgress, () => {
console.error(
'Encountered two children with the same key, `%s`. ' +
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
key,
);
});
break;
case REACT_LAZY_TYPE: {
const resolvedChild = resolveLazy(child as any);
warnOnInvalidKey(
returnFiber,
workInProgress,
resolvedChild,
knownKeys,
);
break;
}
default:
break;
}
}
return knownKeys;
}
// 协调子数组
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>,
lanes: Lanes,
): Fiber | null {
// This algorithm can't optimize by searching from both ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
// with that model. If it ends up not being worth the tradeoffs, we can
// add it later.
//
// 由于纤维上没有反向指针,这个算法不能通过从两端搜索来优化。我想看看用这种模型我们能走
// 多远。如果最终证明不值得权衡,我们以后可以再加上。
// Even with a two ended optimization, we'd want to optimize for the case
// where there are few changes and brute force the comparison instead of
// going for the Map. It'd like to explore hitting that path first in
// forward-only mode and only go for the Map once we notice that we need
// lots of look ahead. This doesn't handle reversal as well as two ended
// search but that's unusual. Besides, for the two ended optimization to
// work on Iterables, we'd need to copy the whole set.
//
// 即使使用双端优化,我们也希望优化那些变化很少的情况,
// 并采用暴力比较而不是使用 Map。我们希望先探索在仅向前模式下
// 的路径,只有在发现需要大量向前查找时才使用 Map。
// 这对于反向处理不如双端搜索,但这种情况不常见。
// 此外,要让双端优化在可迭代对象上工作,我们需要复制整个集合。
// In this first iteration, we'll just live with hitting the bad case
// (adding everything to a Map) in for every insert/move.
//
// 在第一次迭代中,我们只处理最坏的情况
// (在每次插入/移动时将所有内容添加到 Map 中)。
// If you change this code, also update reconcileChildrenIterator() which
// uses the same algorithm.
// 如果你修改了这段代码,也需要更新 reconcileChildrenIterator(),它使用了相同的算法。
let knownKeys: Set<string> | null = null;
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
//
// 待办事项:这在空槽位(如 null 子节点)上会出错。那很不幸,因为它会一直触发慢路径。
// 我们需要一种更好的方式来传达这是未命中还是 null、布尔值、undefined 等。
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
newChildren[newIdx],
knownKeys,
);
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
// 我们匹配了插槽,但没有重用现有的 fiber,所以我们需要删除现有的子节点。
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 待办:移出循环。这只会在第一次运行时发生。
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
//
// 待办事项:如果我们不在该插槽的正确索引处,则延迟处理兄弟节点。
// 也就是说,如果之前有 null 值,那么我们希望针对每个 null 值延迟处理。
// 但是,我们也不希望使用之前的节点调用 updateSlot。
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
// 我们已经处理完新的子项。可以删除其余的。
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
//
// 如果我们没有更多现有的子节点,我们可以选择一条快速路径
// 因为剩下的都会是插入操作。
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
newChildren[newIdx],
knownKeys,
);
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 待办:移出循环。这只会在第一次运行时发生。
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
// 将所有子项添加到键映射中以便快速查找。
const existingChildren = mapRemainingChildren(oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
// 继续扫描,并使用地图将已删除的项目恢复为移动。
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
newChildren[newIdx],
knownKeys,
);
}
if (shouldTrackSideEffects) {
const currenFiber = newFiber.alternate;
if (currentFiber !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
//
// 新的 fiber 仍在开发中,但如果存在一个 current,那就意味着我们重用了
// 该 fiber。我们需要将其从子节点列表中删除,以便不会将其添加到删除列表中。
if (
enableOptimisticKey &&
currentFiber.key === REACT_OPTIMISTIC_KEY
) {
existingChildren.delete(-newIdx - 1);
} else {
existingChildren.delete(
currentFiber.key === null ? newIdx : currentFiber.key,
);
}
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
//
// 上面未被处理的任何现有子项都已被删除。我们需要将它们添加到删除列表中。
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// 协调子元素可迭代对象
function reconcileChildrenIteratable(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildrenIterable: Iterable<mixed>,
lanes: Lanes,
): Fiber | null {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
//
// 这与 reconcileChildrenArray() 的实现相同,
// 但使用的是迭代器。
const iteratorFn = getIteratorFn(newChildrenIterable);
if (typeof iteratorFn !== 'function') {
throw new Error(
'An object is not an iterable. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
const newChildren = iteratorFn.call(newChildrenIterable);
if (__DEV__) {
if (newChildren === newChildrenIterable) {
// We don't support rendering Generators as props because it's a mutation.
// See https://github.com/facebook/react/issues/12995
// We do support generators if they were created by a GeneratorFunction component
// as its direct child since we can recreate those by rerendering the component
// as needed.
//
// 我们不支持将生成器作为属性进行渲染,因为这属于变异操作。
// 详见 https://github.com/facebook/react/issues/12995
// 如果生成器是由 GeneratorFunction 组件创建的,并且作为其直接子元素,
// 我们是支持的,因为可以通过重新渲染组件按需重新创建这些生成器。s
const isGeneratorComponent =
returnFiber.tag === FunctionComponent &&
Object.prototype.toString.call(returnFiber.type) ===
'[object GeneratorFunction]' &&
Object.prototype.toString.call(newChildren) === '[object Generator]';
if (!isGeneratorComponent) {
if (!didWarnAboutGenerators) {
console.error(
'Using Iterators 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. You can also use an ' +
'Iterable that can iterate multiple times over the same items.',
);
}
didWarnAboutGenerators = true;
}
} else if ((newChildrenIterable as any).entries === iteratorFn) {
// Warn about using Maps as children
// 警告:不要将 Maps 用作子元素
if (!didWarnAboutMaps) {
console.error(
'Using Maps as children is not supported. ' +
'Use an array of keyed ReactElements instead.',
);
didWarnAboutMaps = true;
}
}
}
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChildren,
lanes,
);
}
// 异步迭代子项调和
function reconcileChildrenAsyncIteratable(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildrenIterable: AsyncIterable<mixed>,
lanes: Lanes,
): Fiber | null {
const newChildren = newChildrenIterable[ASYNC_ITERATOR]();
if (__DEV__) {
if (newChildren === newChildrenIterable) {
// We don't support rendering AsyncGenerators as props because it's a mutation.
// We do support generators if they were created by a AsyncGeneratorFunction component
// as its direct child since we can recreate those by rerendering the component
// as needed.
//
// 我们不支持将 AsyncGenerators 作为 props 渲染,因为这是一个变更。
// 如果生成器是由 AsyncGeneratorFunction 组件创建的,
// 并且作为其直接子组件存在,我们是支持的,
// 因为我们可以在需要时通过重新渲染组件来重新创建它们。
const isGeneratorComponent =
returnFiber.tag === FunctionComponent &&
Object.prototype.toString.call(returnFiber.type) ===
'[object AsyncGeneratorFunction]' &&
Object.prototype.toString.call(newChildren) ===
'[object AsyncGenerator]';
if (!isGeneratorComponent) {
if (!didWarnAboutGenerators) {
console.error(
'Using AsyncIterators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. ' +
'You can use an AsyncIterable that can iterate multiple times over ' +
'the same items.',
);
}
didWarnAboutGenerators = true;
}
}
}
if (newChildren == null) {
throw new Error('An iterable object provided no iterator.');
}
// To save bytes, we reuse the logic by creating a synchronous Iterable and
// reusing that code path.
//
// 为了节省字节,我们通过创建一个同步可迭代对象并重用该代码路径来复用逻辑。
const iterator: Iterator<mixed> = {
next(): IteratorResult<mixed, void> {
return unwrapThenable(newChildren.next());
},
} as any;
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
iterator,
lanes,
);
}
// 协调子节点迭代器
function reconcileChildrenIterator(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: ?Iterator<mixed>,
lanes: Lanes,
): Fiber | null {
if (newChildren == null) {
throw new Error('An iterable object provided no iterator.');
}
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
let knownKeys: Set<string> | null = null;
let step = newChildren.next();
for (
;
oldFiber !== null && !step.done;
newIdx++, step = newChildren.next()
) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
//
// 待办事项:这在空槽位(如 null 子节点)上会出错。那很不幸,因为它会一直触发慢路径。
// 我们需要一种更好的方式来传达这是未命中还是 null、布尔值、undefined 等。
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
step.value,
knownKeys,
);
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
//
// 我们匹配了插槽,但没有重用现有的 fiber,所以我们需要删除现有的子节点。
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 待办:移出循环。这只会在第一次运行时发生。
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
//
// TODO:如果我们不在此槽位的正确索引上,则推迟处理兄弟节点。
// 也就是说,如果之前存在 null 值,那么我们希望为每个 null 值推迟处理。
// 但是,我们也不希望使用之前的值调用 updateSlot。
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (step.done) {
// We've reached the end of the new children. We can delete the rest.
// 我们已经处理完新的子项。可以删除其余的。
deleteRemainingChildren(returnFiber, oldFiber);
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
//
// 如果我们没有更多现有的子节点,我们可以选择一条快速路径
// 因为剩下的都会是插入操作。
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = createChild(returnFiber, step.value, lanes);
if (newFiber === null) {
continue;
}
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
step.value,
knownKeys,
);
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 待办:移出循环。这只会在第一次运行时发生。
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
// 将所有子项添加到键映射中以便快速查找。
const existingChildren = mapRemainingChildren(oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
// 继续扫描并使用地图将已删除的项目恢复为操作。
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
step.value,
lanes,
);
if (newFiber !== null) {
if (__DEV__) {
knownKeys = warnOnInvalidKey(
returnFiber,
newFiber,
step.value,
knownKeys,
);
}
if (shouldTrackSideEffects) {
const currentFiber = newFiber.alternate;
if (currentFiber !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
//
// 新的 fiber 仍在开发中,但如果存在一个 current,那就意味着我们重用了
// 该 fiber。我们需要将其从子节点列表中删除,以便不会将其添加到删除列表中。
if (
enableOptimisticKey &&
currentFiber.key === REACT_OPTIMISTIC_KEY
) {
existingChildren.delete(-newIdx - 1);
} else {
existingChildren.delete(
currentFiber.key === null ? newIdx : currentFiber.key,
);
}
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
//
// 上面未被处理的任何现有子项都已被删除。我们需要将它们添加到删除列表中。
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
if (getIsHydrating()) {
const numberOfForks = newIdx;
pushTreeFork(returnFiber, numberOfForks);
}
return resultingFirstChild;
}
// 协调单个文本节点
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
lanes: Lanes,
): Fiber {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
// 对于文本节点不需要检查 key,因为我们没有办法去定义它们。
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
//
// 我们已经有一个现有的节点,所以我们只需更新它并删除其余的。
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
//
// 现有的第一个子节点不是文本节点,所以我们需要创建一个新的文本节点
// 并删除已有的文本节点。
deleteRemainingChildren(returnFiber, currentFirstChild);
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
created._debugInfo = currentDebugInfo;
}
return created;
}
// 协调单个元素
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
//
// 待办:如果 key === null 且 child.key === null,则这仅适用于列表中的第一个项目。
if (
child.key === key ||
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)
) {
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
if (enableOptimisticKey) {
// 如果旧的 key 是乐观的,我们现在需要保存真实的 key。
// If the old key was optimistic we need to now save the real one.
existing.key = key;
}
if (enableFragmentRefs) {
coerceRef(existing, element);
}
existing.return = returnFiber;
if (__DEV__) {
existing._debugOwner = element._owner;
existing._debugInfo = currentDebugInfo;
}
validateFragmentProps(element, existing, returnFiber);
return existing;
}
} else {
if (
child.elementType === elementType ||
// Keep this check inline so it only runs on the false path:
// 保持此检查为内联,以便它只在 false 路径上运行:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false) ||
// Lazy types should reconcile their resolved type.
// We need to do this after the Hot Reloading check above,
// because hot reloading has different semantics than prod because
// it doesn't resuspend. So we can't let the call below suspend.
//
// 懒类型应该协调它们解析出的类型。
// 我们需要在上面的热重载检查之后执行此操作,
// 因为热重载的语义不同于生产环境,
// 它不会重新挂起。所以我们不能让下面的调用挂起。
(typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
if (enableOptimisticKey) {
// If the old key was optimistic we need to now save the real one.
// 如果旧的 key 是乐观的,我们现在需要保存真实的 key。
existing.key = key;
}
coerceRef(existing, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugOwner = element._owner;
existing._debugInfo = currentDebugInfo;
}
return existing;
}
}
// Didn't match.
// 不匹配。
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
if (enableFragmentRefs) {
coerceRef(created, element);
}
created.return = returnFiber;
if (__DEV__) {
// We treat the parent as the owner for stack purposes.
// 为堆栈目的,我们将父项视为所有者。
created._debugOwner = returnFiber;
created._debugTask = returnFiber._debugTask;
created._debugInfo = currentDebugInfo;
}
validateFragmentProps(element, created, returnFiber);
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(created, element);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = currentDebugInfo;
}
return created;
}
}
// 调和单一门户
function reconcileSinglePortal(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
lanes: Lanes,
): Fiber {
const key = portal.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
//
// 待办:如果 key === null 且 child.key === null,那么这只适用于列表中的第一个项目。
if (
child.key === key ||
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)
) {
if (
child.tag === HostPortal &&
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, portal.children || []);
if (enableOptimisticKey) {
// If the old key was optimistic we need to now save the real one.
// 如果旧的 key 是乐观的,我们现在需要保存真实的 key。
existing.key = key;
}
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
//
// 这个 API 会给子节点打上副作用标签,作为协调过程本身的副作用。
// 当我们遍历子节点和父节点时,它们会被加入到副作用列表中。
// 协调子 Fiber 实现
function reconcileChildFibersImpl(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// This function is only recursive for Usables/Lazy and not nested arrays.
// That's so that using a Lazy wrapper is unobservable to the Fragment
// convention.
//
// 这个函数仅对 Usables/Lazy 递归,而不是嵌套数组。
// 这是为了使使用 Lazy 包装器对 Fragment 约定不可见。
//
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.
//
// 如果顶层项目是数组,我们将其视为一组子元素,
// 而不是片段。另一方面,嵌套数组将被视为
// 片段节点。递归在正常流程中发生。
// Handle top level unkeyed fragments without refs (enableFragmentRefs)
// as if they were arrays. This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
// We don't use recursion here because a fragment inside a fragment
// is no longer considered "top level" for these purposes.
//
// 处理没有 ref 的顶层未键控片段(enableFragmentRefs)
// 就像它们是数组一样。这会导致 <>{[...]}</> 和 <>...</> 之间的歧义。
// 我们对上述歧义情况进行相同处理。
// 我们这里不使用递归,因为片段内部的片段
// 对于这些目的不再被认为是“顶层”的。
const isUnkeyedUnrefedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null &&
(enableFragmentRefs ? newChild.props.ref === undefined : true);
if (isUnkeyedUnrefedTopLevelFragment) {
validateFragmentProps(newChild, null, returnFiber);
newChild = newChild.props.children;
}
// Handle object types
// 处理对象类型
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const firstChild = placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
case REACT_LAZY_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const result = resolveLazy(newChild as any);
const firstChild = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
result,
lanes,
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
}
if (isArray(newChild)) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const firstChild = reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
if (getIteratorFn(newChild)) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const firstChild = reconcileChildrenIteratable(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
if (
enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function'
) {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const firstChild = reconcileChildrenAsyncIteratable(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
// Usables are a valid React node type. When React encounters a Usable in
// a child position, it unwraps it using the same algorithm as `use`. For
// example, for promises, React will throw an exception to unwind the
// stack, then replay the component once the promise resolves.
//
// Usables 是一种有效的 React 节点类型。当 React 在子节点位置遇到 Usable 时,
// 它会使用与 `use` 相同的算法将其解包。
// 例如,对于 Promise,React 会抛出异常以展开堆栈,然后在 Promise 解决后重新渲染组件。
//
// A difference from `use` is that React will keep unwrapping the value
// until it reaches a non-Usable type.
//
// 与 `use` 的不同之处在于,React 会不断解开值
// 直到它达到一个不可用的类型。
//
// e.g. Usable<Usable<Usable<T>>> should resolve to T
// 例如,Usable<Usable<Usable<T>>> 应该解析为 T
//
// The structure is a bit unfortunate. Ideally, we shouldn't need to
// replay the entire begin phase of the parent fiber in order to reconcile
// the children again. This would require a somewhat significant refactor,
// because reconcilation happens deep within the begin phase, and
// depending on the type of work, not always at the end. We should
// consider as an future improvement.
//
// 这个结构有点不太理想。理想情况下,我们不应该需要
// 重新执行父 fiber 的整个 begin 阶段来再次协调子节点。
// 这将需要相当大规模的重构,
// 因为协调发生在 begin 阶段的深处,
// 并且根据工作类型,并不总是在末尾。
// 我们应该考虑作为未来的改进。
if (typeof newChild.then === 'function') {
const thenable: Thenable<any> = newChild as any;
const prevDebugInfo = pushDebugInfo((thenable as any)._debugInfo);
const firstChild = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
unwrapThenable(thenable),
lanes,
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<mixed> = newChild as any;
return reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
readContextDuringReconciliation(returnFiber, context, lanes),
lanes,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType(returnFiber, newChild);
}
if (typeof newChild === 'symbol') {
warnOnSymbolType(returnFiber, newChild);
}
}
// Remaining cases are all treated as empty.
// 剩下的情况都被视为空。
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
// 协调子 Fiber
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const prevDebugInfo = currentDebugInfo;
currentDebugInfo = null;
try {
// This indirection only exists so we can reset `thenableState` at the end.
// It should get inlined by Closure.
//
// 这个间接引用的存在只是为了我们能够在最后重置 `thenableState`。
// 它应该会被 Closure 内联。
thenableIndexCounter = 0;
const firstChildFiber = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
thenableState = null;
// Don't bother to reset `thenableIndexCounter` to 0 because it always gets
// set at the beginning.
//
// 不必费心将 `thenableIndexCounter` 重置为 0,因为它总是在开始时被设置。
return firstChildFiber;
} catch (x) {
if (
x === SuspenseException ||
x === SuspenseActionException ||
(!disableLegacyMode &&
(returnFiber.mode & ConcurrentMode) === NoMode &&
typeof x === 'object' &&
x !== null &&
typeof x.then === 'function')
) {
// Suspense exceptions need to read the current suspended state before
// yielding and replay it using the same sequence so this trick doesn't
// work here.
//
// 挂起异常需要在暂停前读取当前挂起状态
// 并使用相同的顺序重放它,所以这个技巧在这里不起作用。
//
// Suspending in legacy mode actually mounts so if we let the child
// mount then we delete its state in an update.
//
// 在遗留模式下挂起实际上会进行挂载,因此如果我们让子组件挂载,那么我们会在更新
// 中删除它的状态。
throw x;
}
// Something errored during reconciliation but it's conceptually a child that
// errored and not the current component itself so we create a virtual child
// that throws in its begin phase. That way the current component can handle
// the error or suspending if needed.
//
// 在协调期间出现了某些错误,但从概念上来说,这是子组件出错,而不是当前组件本身,所以我们
// 创建一个在其开始阶段就会抛出错误的虚拟子组件。这样,当前组件就可以根据需要处理错误或挂起。
const throwFiber = createFiberFromThrow(x, returnFiber.mode, lanes);
throwFiber.return = returnFiber;
if (__DEV__) {
const debugInfo = (throwFiber._debugInfo = currentDebugInfo);
// Conceptually the error's owner should ideally be captured when the
// Error constructor is called but we don't override them to capture our
// `owner`. So instead, we use the nearest parent as the owner/task of the
// error. This is usually the same thing when it's thrown from the same
// async component but not if you await a promise started from a different
// component/task.
//
// 从概念上讲,错误的所有者理想情况下应该在调用 Error 构造函数时被捕获,但我们并没有
// 重写它们来捕获我们的 `owner`。所以我们改为使用最近的父级作为错误的所有者/任务。当
// 错误从同一个异步组件抛出时,这通常是相同的,但如果你等待一个由不同组件/任务启动
// 的 promise,则情况就不同。
//
// In newer Chrome, Error constructor does capture the Task which is what
// is logged by reportError. In that case this debugTask isn't used.
//
// 在更新的 Chrome 中,Error 构造函数确实会捕获任务,这就是 reportError 记录的
// 内容。在这种情况下,这个 debugTask 并未使用。
throwFiber._debugOwner = returnFiber._debugOwner;
throwFiber._debugTask = returnFiber._debugTask;
if (debugInfo != null) {
for (let i = debugInfo.length - 1; i >= 0; i--) {
if (typeof debugInfo[i].stack === 'string') {
throwFiber._debugOwner = debugInfo[i] as any;
throwFiber._debugTask = debugInfo[i].debugTask;
break;
}
}
}
}
return throwFiber;
} finally {
currentDebugInfo = prevDebugInfo;
}
}
return reconcileChildFibers;
}
13. 验证暂挂列表嵌套子项
function validateSuspenseListNestedChild(childSlot: mixed, index: number) {
if (__DEV__) {
const isAnArray = isArray(childSlot);
const isIterable =
!isAnArray && typeof getIteratorFn(childSlot) === 'function';
const isAsyncIterable =
enableAsyncIterableChildren &&
typeof childSlot === 'object' &&
childSlot !== null &&
typeof (childSlot as any)[ASYNC_ITERATOR] === 'function';
if (isAnArray || isIterable || isAsyncIterable) {
const type = isAnArray
? 'array'
: isAsyncIterable
? 'async iterable'
: 'iterable';
console.error(
'A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' +
'an additional SuspenseList to configure its revealOrder: ' +
'<SuspenseList revealOrder=...> ... ' +
'<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' +
'</SuspenseList>',
type,
index,
type,
);
return false;
}
}
return true;
}