1458 lines
45 KiB
JavaScript
1458 lines
45 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactElement} from 'shared/ReactElementType';
|
|
import type {ReactPortal} from 'shared/ReactTypes';
|
|
import type {Fiber} from './ReactFiber';
|
|
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
|
|
|
import getComponentName from 'shared/getComponentName';
|
|
import {Placement, Deletion} from 'shared/ReactSideEffectTags';
|
|
import {
|
|
getIteratorFn,
|
|
REACT_ELEMENT_TYPE,
|
|
REACT_FRAGMENT_TYPE,
|
|
REACT_PORTAL_TYPE,
|
|
REACT_CHUNK_TYPE,
|
|
} from 'shared/ReactSymbols';
|
|
import {
|
|
FunctionComponent,
|
|
ClassComponent,
|
|
HostText,
|
|
HostPortal,
|
|
Fragment,
|
|
Chunk,
|
|
} from 'shared/ReactWorkTags';
|
|
import invariant from 'shared/invariant';
|
|
import {warnAboutStringRefs, enableChunksAPI} from 'shared/ReactFeatureFlags';
|
|
|
|
import {
|
|
createWorkInProgress,
|
|
resetWorkInProgress,
|
|
createFiberFromElement,
|
|
createFiberFromFragment,
|
|
createFiberFromText,
|
|
createFiberFromPortal,
|
|
} from './ReactFiber';
|
|
import {emptyRefsObject} from './ReactFiberClassComponent';
|
|
import {
|
|
getCurrentFiberStackInDev,
|
|
getStackByFiberInDevAndProd,
|
|
} from './ReactCurrentFiber';
|
|
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading';
|
|
import {StrictMode} from './ReactTypeOfMode';
|
|
|
|
let didWarnAboutMaps;
|
|
let didWarnAboutGenerators;
|
|
let didWarnAboutStringRefs;
|
|
let ownerHasKeyUseWarning;
|
|
let ownerHasFunctionTypeWarning;
|
|
let warnForMissingKey = (child: mixed) => {};
|
|
|
|
if (__DEV__) {
|
|
didWarnAboutMaps = false;
|
|
didWarnAboutGenerators = false;
|
|
didWarnAboutStringRefs = {};
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
ownerHasKeyUseWarning = {};
|
|
ownerHasFunctionTypeWarning = {};
|
|
|
|
warnForMissingKey = (child: mixed) => {
|
|
if (child === null || typeof child !== 'object') {
|
|
return;
|
|
}
|
|
if (!child._store || child._store.validated || child.key != null) {
|
|
return;
|
|
}
|
|
invariant(
|
|
typeof child._store === 'object',
|
|
'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 = true;
|
|
|
|
const currentComponentErrorInfo =
|
|
'Each child in a list should have a unique ' +
|
|
'"key" prop. See https://fb.me/react-warning-keys for ' +
|
|
'more information.' +
|
|
getCurrentFiberStackInDev();
|
|
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
|
|
return;
|
|
}
|
|
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
|
|
|
|
console.error(
|
|
'Each child in a list should have a unique ' +
|
|
'"key" prop. See https://fb.me/react-warning-keys for ' +
|
|
'more information.',
|
|
);
|
|
};
|
|
}
|
|
|
|
const isArray = Array.isArray;
|
|
|
|
function coerceRef(
|
|
returnFiber: Fiber,
|
|
current: Fiber | null,
|
|
element: ReactElement,
|
|
) {
|
|
let mixedRef = element.ref;
|
|
if (
|
|
mixedRef !== null &&
|
|
typeof mixedRef !== 'function' &&
|
|
typeof mixedRef !== 'object'
|
|
) {
|
|
if (__DEV__) {
|
|
// TODO: Clean this up once we turn on the string ref warning for
|
|
// everyone, because the strict mode case will no longer be relevant
|
|
if (
|
|
(returnFiber.mode & StrictMode || warnAboutStringRefs) &&
|
|
// We warn in ReactElement.js if owner and self are equal for string refs
|
|
// because these cannot be automatically converted to an arrow function
|
|
// using a codemod. Therefore, we don't have to warn about string refs again.
|
|
!(
|
|
element._owner &&
|
|
element._self &&
|
|
element._owner.stateNode !== element._self
|
|
)
|
|
) {
|
|
const componentName = getComponentName(returnFiber.type) || 'Component';
|
|
if (!didWarnAboutStringRefs[componentName]) {
|
|
if (warnAboutStringRefs) {
|
|
console.error(
|
|
'Component "%s" contains the string ref "%s". Support for string refs ' +
|
|
'will be removed in a future major release. We recommend using ' +
|
|
'useRef() or createRef() instead. ' +
|
|
'Learn more about using refs safely here: ' +
|
|
'https://fb.me/react-strict-mode-string-ref%s',
|
|
componentName,
|
|
mixedRef,
|
|
getStackByFiberInDevAndProd(returnFiber),
|
|
);
|
|
} else {
|
|
console.error(
|
|
'A string ref, "%s", has been found within a strict mode tree. ' +
|
|
'String refs are a source of potential bugs and should be avoided. ' +
|
|
'We recommend using useRef() or createRef() instead. ' +
|
|
'Learn more about using refs safely here: ' +
|
|
'https://fb.me/react-strict-mode-string-ref%s',
|
|
mixedRef,
|
|
getStackByFiberInDevAndProd(returnFiber),
|
|
);
|
|
}
|
|
didWarnAboutStringRefs[componentName] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (element._owner) {
|
|
const owner: ?Fiber = (element._owner: any);
|
|
let inst;
|
|
if (owner) {
|
|
const ownerFiber = ((owner: any): Fiber);
|
|
invariant(
|
|
ownerFiber.tag === ClassComponent,
|
|
'Function components cannot have refs. ' +
|
|
'Did you mean to use React.forwardRef()?',
|
|
);
|
|
inst = ownerFiber.stateNode;
|
|
}
|
|
invariant(
|
|
inst,
|
|
'Missing owner for string ref %s. This error is likely caused by a ' +
|
|
'bug in React. Please file an issue.',
|
|
mixedRef,
|
|
);
|
|
const stringRef = '' + mixedRef;
|
|
// Check if previous string ref matches new string ref
|
|
if (
|
|
current !== null &&
|
|
current.ref !== null &&
|
|
typeof current.ref === 'function' &&
|
|
current.ref._stringRef === stringRef
|
|
) {
|
|
return current.ref;
|
|
}
|
|
const ref = function(value) {
|
|
let refs = inst.refs;
|
|
if (refs === emptyRefsObject) {
|
|
// This is a lazy pooled frozen object, so we need to initialize.
|
|
refs = inst.refs = {};
|
|
}
|
|
if (value === null) {
|
|
delete refs[stringRef];
|
|
} else {
|
|
refs[stringRef] = value;
|
|
}
|
|
};
|
|
ref._stringRef = stringRef;
|
|
return ref;
|
|
} else {
|
|
invariant(
|
|
typeof mixedRef === 'string',
|
|
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
|
|
);
|
|
invariant(
|
|
element._owner,
|
|
'Element ref was specified as a string (%s) but no owner was set. This could happen for one of' +
|
|
' the following reasons:\n' +
|
|
'1. You may be adding a ref to a function component\n' +
|
|
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
|
|
'3. You have multiple copies of React loaded\n' +
|
|
'See https://fb.me/react-refs-must-have-owner for more information.',
|
|
mixedRef,
|
|
);
|
|
}
|
|
}
|
|
return mixedRef;
|
|
}
|
|
|
|
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
|
|
if (returnFiber.type !== 'textarea') {
|
|
let addendum = '';
|
|
if (__DEV__) {
|
|
addendum =
|
|
' If you meant to render a collection of children, use an array ' +
|
|
'instead.' +
|
|
getCurrentFiberStackInDev();
|
|
}
|
|
invariant(
|
|
false,
|
|
'Objects are not valid as a React child (found: %s).%s',
|
|
Object.prototype.toString.call(newChild) === '[object Object]'
|
|
? 'object with keys {' + Object.keys(newChild).join(', ') + '}'
|
|
: newChild,
|
|
addendum,
|
|
);
|
|
}
|
|
}
|
|
|
|
function warnOnFunctionType() {
|
|
if (__DEV__) {
|
|
const currentComponentErrorInfo =
|
|
'Functions are not valid as a React child. This may happen if ' +
|
|
'you return a Component instead of <Component /> from render. ' +
|
|
'Or maybe you meant to call this function rather than return it.' +
|
|
getCurrentFiberStackInDev();
|
|
|
|
if (ownerHasFunctionTypeWarning[currentComponentErrorInfo]) {
|
|
return;
|
|
}
|
|
ownerHasFunctionTypeWarning[currentComponentErrorInfo] = true;
|
|
|
|
console.error(
|
|
'Functions are not valid as a React child. This may happen if ' +
|
|
'you return a Component instead of <Component /> from render. ' +
|
|
'Or maybe you meant to call this function rather than return it.',
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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 ChildReconciler(shouldTrackSideEffects) {
|
|
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
|
|
if (!shouldTrackSideEffects) {
|
|
// Noop.
|
|
return;
|
|
}
|
|
// Deletions are added in reversed order so we add it to the front.
|
|
// At this point, the return fiber's effect list is empty except for
|
|
// deletions, so we can just append the deletion to the list. The remaining
|
|
// effects aren't added until the complete phase. Once we implement
|
|
// resuming, this may not be true.
|
|
const last = returnFiber.lastEffect;
|
|
if (last !== null) {
|
|
last.nextEffect = childToDelete;
|
|
returnFiber.lastEffect = childToDelete;
|
|
} else {
|
|
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
|
|
}
|
|
childToDelete.nextEffect = null;
|
|
childToDelete.effectTag = Deletion;
|
|
}
|
|
|
|
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.
|
|
let childToDelete = currentFirstChild;
|
|
while (childToDelete !== null) {
|
|
deleteChild(returnFiber, childToDelete);
|
|
childToDelete = childToDelete.sibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function mapRemainingChildren(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber,
|
|
): Map<string | number, 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.
|
|
const existingChildren: Map<string | number, Fiber> = new Map();
|
|
|
|
let existingChild = currentFirstChild;
|
|
while (existingChild !== null) {
|
|
if (existingChild.key !== null) {
|
|
existingChildren.set(existingChild.key, existingChild);
|
|
} else {
|
|
existingChildren.set(existingChild.index, existingChild);
|
|
}
|
|
existingChild = existingChild.sibling;
|
|
}
|
|
return existingChildren;
|
|
}
|
|
|
|
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.
|
|
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) {
|
|
// Noop.
|
|
return lastPlacedIndex;
|
|
}
|
|
const current = newFiber.alternate;
|
|
if (current !== null) {
|
|
const oldIndex = current.index;
|
|
if (oldIndex < lastPlacedIndex) {
|
|
// This is a move.
|
|
newFiber.effectTag = Placement;
|
|
return lastPlacedIndex;
|
|
} else {
|
|
// This item can stay in place.
|
|
return oldIndex;
|
|
}
|
|
} else {
|
|
// This is an insertion.
|
|
newFiber.effectTag = Placement;
|
|
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.effectTag = Placement;
|
|
}
|
|
return newFiber;
|
|
}
|
|
|
|
function updateTextNode(
|
|
returnFiber: Fiber,
|
|
current: Fiber | null,
|
|
textContent: string,
|
|
expirationTime: ExpirationTime,
|
|
) {
|
|
if (current === null || current.tag !== HostText) {
|
|
// Insert
|
|
const created = createFiberFromText(
|
|
textContent,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
const existing = useFiber(current, textContent);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function updateElement(
|
|
returnFiber: Fiber,
|
|
current: Fiber | null,
|
|
element: ReactElement,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber {
|
|
if (current !== null) {
|
|
if (
|
|
current.elementType === element.type ||
|
|
// Keep this check inline so it only runs on the false path:
|
|
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
|
|
) {
|
|
// Move based on index
|
|
const existing = useFiber(current, element.props);
|
|
existing.ref = coerceRef(returnFiber, current, element);
|
|
existing.return = returnFiber;
|
|
if (__DEV__) {
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
return existing;
|
|
} else if (
|
|
enableChunksAPI &&
|
|
current.tag === Chunk &&
|
|
element.type.$$typeof === REACT_CHUNK_TYPE &&
|
|
element.type.render === current.type.render
|
|
) {
|
|
// Same as above but also update the .type field.
|
|
const existing = useFiber(current, element.props);
|
|
existing.return = returnFiber;
|
|
existing.type = element.type;
|
|
if (__DEV__) {
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
return existing;
|
|
}
|
|
}
|
|
// Insert
|
|
const created = createFiberFromElement(
|
|
element,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.ref = coerceRef(returnFiber, current, element);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
function updatePortal(
|
|
returnFiber: Fiber,
|
|
current: Fiber | null,
|
|
portal: ReactPortal,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber {
|
|
if (
|
|
current === null ||
|
|
current.tag !== HostPortal ||
|
|
current.stateNode.containerInfo !== portal.containerInfo ||
|
|
current.stateNode.implementation !== portal.implementation
|
|
) {
|
|
// Insert
|
|
const created = createFiberFromPortal(
|
|
portal,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
const existing = useFiber(current, portal.children || []);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function updateFragment(
|
|
returnFiber: Fiber,
|
|
current: Fiber | null,
|
|
fragment: Iterable<*>,
|
|
expirationTime: ExpirationTime,
|
|
key: null | string,
|
|
): Fiber {
|
|
if (current === null || current.tag !== Fragment) {
|
|
// Insert
|
|
const created = createFiberFromFragment(
|
|
fragment,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
key,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
// Update
|
|
const existing = useFiber(current, fragment);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
}
|
|
}
|
|
|
|
function createChild(
|
|
returnFiber: Fiber,
|
|
newChild: any,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber | null {
|
|
if (typeof newChild === 'string' || typeof newChild === 'number') {
|
|
// 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,
|
|
expirationTime,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
if (typeof newChild === 'object' && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
const created = createFiberFromElement(
|
|
newChild,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.ref = coerceRef(returnFiber, null, newChild);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
case REACT_PORTAL_TYPE: {
|
|
const created = createFiberFromPortal(
|
|
newChild,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
const created = createFiberFromFragment(
|
|
newChild,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
null,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (typeof newChild === 'function') {
|
|
warnOnFunctionType();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateSlot(
|
|
returnFiber: Fiber,
|
|
oldFiber: Fiber | null,
|
|
newChild: any,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber | null {
|
|
// Update the fiber if the keys match, otherwise return null.
|
|
|
|
const key = oldFiber !== null ? oldFiber.key : null;
|
|
|
|
if (typeof newChild === 'string' || typeof newChild === 'number') {
|
|
// 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,
|
|
expirationTime,
|
|
);
|
|
}
|
|
|
|
if (typeof newChild === 'object' && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
if (newChild.key === key) {
|
|
if (newChild.type === REACT_FRAGMENT_TYPE) {
|
|
return updateFragment(
|
|
returnFiber,
|
|
oldFiber,
|
|
newChild.props.children,
|
|
expirationTime,
|
|
key,
|
|
);
|
|
}
|
|
return updateElement(
|
|
returnFiber,
|
|
oldFiber,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
case REACT_PORTAL_TYPE: {
|
|
if (newChild.key === key) {
|
|
return updatePortal(
|
|
returnFiber,
|
|
oldFiber,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
if (key !== null) {
|
|
return null;
|
|
}
|
|
|
|
return updateFragment(
|
|
returnFiber,
|
|
oldFiber,
|
|
newChild,
|
|
expirationTime,
|
|
null,
|
|
);
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (typeof newChild === 'function') {
|
|
warnOnFunctionType();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function updateFromMap(
|
|
existingChildren: Map<string | number, Fiber>,
|
|
returnFiber: Fiber,
|
|
newIdx: number,
|
|
newChild: any,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber | null {
|
|
if (typeof newChild === 'string' || typeof newChild === 'number') {
|
|
// 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,
|
|
expirationTime,
|
|
);
|
|
}
|
|
|
|
if (typeof newChild === 'object' && newChild !== null) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE: {
|
|
const matchedFiber =
|
|
existingChildren.get(
|
|
newChild.key === null ? newIdx : newChild.key,
|
|
) || null;
|
|
if (newChild.type === REACT_FRAGMENT_TYPE) {
|
|
return updateFragment(
|
|
returnFiber,
|
|
matchedFiber,
|
|
newChild.props.children,
|
|
expirationTime,
|
|
newChild.key,
|
|
);
|
|
}
|
|
return updateElement(
|
|
returnFiber,
|
|
matchedFiber,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
}
|
|
case REACT_PORTAL_TYPE: {
|
|
const matchedFiber =
|
|
existingChildren.get(
|
|
newChild.key === null ? newIdx : newChild.key,
|
|
) || null;
|
|
return updatePortal(
|
|
returnFiber,
|
|
matchedFiber,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) {
|
|
const matchedFiber = existingChildren.get(newIdx) || null;
|
|
return updateFragment(
|
|
returnFiber,
|
|
matchedFiber,
|
|
newChild,
|
|
expirationTime,
|
|
null,
|
|
);
|
|
}
|
|
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (typeof newChild === 'function') {
|
|
warnOnFunctionType();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Warns if there is a duplicate or missing key
|
|
*/
|
|
function warnOnInvalidKey(
|
|
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(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;
|
|
}
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return knownKeys;
|
|
}
|
|
|
|
function reconcileChildrenArray(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
newChildren: Array<*>,
|
|
expirationTime: ExpirationTime,
|
|
): 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.
|
|
|
|
// In this first iteration, we'll just live with hitting the bad case
|
|
// (adding everything to a Map) in for every insert/move.
|
|
|
|
// If you change this code, also update reconcileChildrenIterator() which
|
|
// uses the same algorithm.
|
|
|
|
if (__DEV__) {
|
|
// First, validate keys.
|
|
let knownKeys = null;
|
|
for (let i = 0; i < newChildren.length; i++) {
|
|
const child = newChildren[i];
|
|
knownKeys = warnOnInvalidKey(child, knownKeys);
|
|
}
|
|
}
|
|
|
|
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],
|
|
expirationTime,
|
|
);
|
|
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.
|
|
if (oldFiber === null) {
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
break;
|
|
}
|
|
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.
|
|
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.
|
|
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);
|
|
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],
|
|
expirationTime,
|
|
);
|
|
if (newFiber === null) {
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
// Add all children to a key map for quick lookups.
|
|
const existingChildren = mapRemainingChildren(returnFiber, 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],
|
|
expirationTime,
|
|
);
|
|
if (newFiber !== null) {
|
|
if (shouldTrackSideEffects) {
|
|
if (newFiber.alternate !== 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.
|
|
existingChildren.delete(
|
|
newFiber.key === null ? newIdx : newFiber.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));
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
function reconcileChildrenIterator(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
newChildrenIterable: Iterable<*>,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber | null {
|
|
// This is the same implementation as reconcileChildrenArray(),
|
|
// but using the iterator instead.
|
|
|
|
const iteratorFn = getIteratorFn(newChildrenIterable);
|
|
invariant(
|
|
typeof iteratorFn === 'function',
|
|
'An object is not an iterable. This error is likely caused by a bug in ' +
|
|
'React. Please file an issue.',
|
|
);
|
|
|
|
if (__DEV__) {
|
|
// We don't support rendering Generators because it's a mutation.
|
|
// See https://github.com/facebook/react/issues/12995
|
|
if (
|
|
typeof Symbol === 'function' &&
|
|
// $FlowFixMe Flow doesn't know about toStringTag
|
|
newChildrenIterable[Symbol.toStringTag] === 'Generator'
|
|
) {
|
|
if (!didWarnAboutGenerators) {
|
|
console.error(
|
|
'Using Generators as children is unsupported and will likely yield ' +
|
|
'unexpected results because enumerating a generator mutates it. ' +
|
|
'You may convert it to an array with `Array.from()` or the ' +
|
|
'`[...spread]` operator before rendering. Keep in mind ' +
|
|
'you might need to polyfill these features for older browsers.',
|
|
);
|
|
}
|
|
didWarnAboutGenerators = true;
|
|
}
|
|
|
|
// Warn about using Maps as children
|
|
if ((newChildrenIterable: any).entries === iteratorFn) {
|
|
if (!didWarnAboutMaps) {
|
|
console.error(
|
|
'Using Maps as children is unsupported and will likely yield ' +
|
|
'unexpected results. Convert it to a sequence/iterable of keyed ' +
|
|
'ReactElements instead.',
|
|
);
|
|
}
|
|
didWarnAboutMaps = true;
|
|
}
|
|
|
|
// First, validate keys.
|
|
// We'll get a different iterator later for the main pass.
|
|
const newChildren = iteratorFn.call(newChildrenIterable);
|
|
if (newChildren) {
|
|
let knownKeys = null;
|
|
let step = newChildren.next();
|
|
for (; !step.done; step = newChildren.next()) {
|
|
const child = step.value;
|
|
knownKeys = warnOnInvalidKey(child, knownKeys);
|
|
}
|
|
}
|
|
}
|
|
|
|
const newChildren = iteratorFn.call(newChildrenIterable);
|
|
invariant(newChildren != null, '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 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,
|
|
expirationTime,
|
|
);
|
|
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.
|
|
if (oldFiber === null) {
|
|
oldFiber = nextOldFiber;
|
|
}
|
|
break;
|
|
}
|
|
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.
|
|
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.
|
|
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);
|
|
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, expirationTime);
|
|
if (newFiber === null) {
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
// Add all children to a key map for quick lookups.
|
|
const existingChildren = mapRemainingChildren(returnFiber, 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,
|
|
expirationTime,
|
|
);
|
|
if (newFiber !== null) {
|
|
if (shouldTrackSideEffects) {
|
|
if (newFiber.alternate !== 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.
|
|
existingChildren.delete(
|
|
newFiber.key === null ? newIdx : newFiber.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));
|
|
}
|
|
|
|
return resultingFirstChild;
|
|
}
|
|
|
|
function reconcileSingleTextNode(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
textContent: string,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber {
|
|
// There's no need to check for keys on text nodes since we don't have a
|
|
// way to define them.
|
|
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,
|
|
expirationTime,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
|
|
function reconcileSingleElement(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
element: ReactElement,
|
|
expirationTime: ExpirationTime,
|
|
): 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.
|
|
if (child.key === key) {
|
|
switch (child.tag) {
|
|
case Fragment: {
|
|
if (element.type === REACT_FRAGMENT_TYPE) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
const existing = useFiber(child, element.props.children);
|
|
existing.return = returnFiber;
|
|
if (__DEV__) {
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
return existing;
|
|
}
|
|
break;
|
|
}
|
|
case Chunk:
|
|
if (enableChunksAPI) {
|
|
if (
|
|
element.type.$$typeof === REACT_CHUNK_TYPE &&
|
|
element.type.render === child.type.render
|
|
) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
const existing = useFiber(child, element.props);
|
|
existing.type = element.type;
|
|
existing.return = returnFiber;
|
|
if (__DEV__) {
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
return existing;
|
|
}
|
|
}
|
|
// We intentionally fallthrough here if enableChunksAPI is not on.
|
|
// eslint-disable-next-lined no-fallthrough
|
|
default: {
|
|
if (
|
|
child.elementType === element.type ||
|
|
// Keep this check inline so it only runs on the false path:
|
|
(__DEV__
|
|
? isCompatibleFamilyForHotReloading(child, element)
|
|
: false)
|
|
) {
|
|
deleteRemainingChildren(returnFiber, child.sibling);
|
|
const existing = useFiber(child, element.props);
|
|
existing.ref = coerceRef(returnFiber, child, element);
|
|
existing.return = returnFiber;
|
|
if (__DEV__) {
|
|
existing._debugSource = element._source;
|
|
existing._debugOwner = element._owner;
|
|
}
|
|
return existing;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// 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,
|
|
expirationTime,
|
|
element.key,
|
|
);
|
|
created.return = returnFiber;
|
|
return created;
|
|
} else {
|
|
const created = createFiberFromElement(
|
|
element,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
created.ref = coerceRef(returnFiber, currentFirstChild, element);
|
|
created.return = returnFiber;
|
|
return created;
|
|
}
|
|
}
|
|
|
|
function reconcileSinglePortal(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
portal: ReactPortal,
|
|
expirationTime: ExpirationTime,
|
|
): 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.
|
|
if (child.key === 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 || []);
|
|
existing.return = returnFiber;
|
|
return existing;
|
|
} else {
|
|
deleteRemainingChildren(returnFiber, child);
|
|
break;
|
|
}
|
|
} else {
|
|
deleteChild(returnFiber, child);
|
|
}
|
|
child = child.sibling;
|
|
}
|
|
|
|
const created = createFiberFromPortal(
|
|
portal,
|
|
returnFiber.mode,
|
|
expirationTime,
|
|
);
|
|
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.
|
|
function reconcileChildFibers(
|
|
returnFiber: Fiber,
|
|
currentFirstChild: Fiber | null,
|
|
newChild: any,
|
|
expirationTime: ExpirationTime,
|
|
): Fiber | null {
|
|
// This function is not recursive.
|
|
// 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 as if they were arrays.
|
|
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
|
|
// We treat the ambiguous cases above the same.
|
|
const isUnkeyedTopLevelFragment =
|
|
typeof newChild === 'object' &&
|
|
newChild !== null &&
|
|
newChild.type === REACT_FRAGMENT_TYPE &&
|
|
newChild.key === null;
|
|
if (isUnkeyedTopLevelFragment) {
|
|
newChild = newChild.props.children;
|
|
}
|
|
|
|
// Handle object types
|
|
const isObject = typeof newChild === 'object' && newChild !== null;
|
|
|
|
if (isObject) {
|
|
switch (newChild.$$typeof) {
|
|
case REACT_ELEMENT_TYPE:
|
|
return placeSingleChild(
|
|
reconcileSingleElement(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
expirationTime,
|
|
),
|
|
);
|
|
case REACT_PORTAL_TYPE:
|
|
return placeSingleChild(
|
|
reconcileSinglePortal(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
expirationTime,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
if (typeof newChild === 'string' || typeof newChild === 'number') {
|
|
return placeSingleChild(
|
|
reconcileSingleTextNode(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
'' + newChild,
|
|
expirationTime,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (isArray(newChild)) {
|
|
return reconcileChildrenArray(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
}
|
|
|
|
if (getIteratorFn(newChild)) {
|
|
return reconcileChildrenIterator(
|
|
returnFiber,
|
|
currentFirstChild,
|
|
newChild,
|
|
expirationTime,
|
|
);
|
|
}
|
|
|
|
if (isObject) {
|
|
throwOnInvalidObjectType(returnFiber, newChild);
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (typeof newChild === 'function') {
|
|
warnOnFunctionType();
|
|
}
|
|
}
|
|
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
|
|
// If the new child is undefined, and the return fiber is a composite
|
|
// component, throw an error. If Fiber return types are disabled,
|
|
// we already threw above.
|
|
switch (returnFiber.tag) {
|
|
case ClassComponent: {
|
|
if (__DEV__) {
|
|
const instance = returnFiber.stateNode;
|
|
if (instance.render._isMockFunction) {
|
|
// We allow auto-mocks to proceed as if they're returning null.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Intentionally fall through to the next case, which handles both
|
|
// functions and classes
|
|
// eslint-disable-next-lined no-fallthrough
|
|
case FunctionComponent: {
|
|
const Component = returnFiber.type;
|
|
invariant(
|
|
false,
|
|
'%s(...): Nothing was returned from render. This usually means a ' +
|
|
'return statement is missing. Or, to render nothing, ' +
|
|
'return null.',
|
|
Component.displayName || Component.name || 'Component',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remaining cases are all treated as empty.
|
|
return deleteRemainingChildren(returnFiber, currentFirstChild);
|
|
}
|
|
|
|
return reconcileChildFibers;
|
|
}
|
|
|
|
export const reconcileChildFibers = ChildReconciler(true);
|
|
export const mountChildFibers = ChildReconciler(false);
|
|
|
|
export function cloneChildFibers(
|
|
current: Fiber | null,
|
|
workInProgress: Fiber,
|
|
): void {
|
|
invariant(
|
|
current === null || workInProgress.child === current.child,
|
|
'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;
|
|
}
|
|
|
|
// Reset a workInProgress child set to prepare it for a second pass.
|
|
export function resetChildFibers(
|
|
workInProgress: Fiber,
|
|
renderExpirationTime: ExpirationTime,
|
|
): void {
|
|
let child = workInProgress.child;
|
|
while (child !== null) {
|
|
resetWorkInProgress(child, renderExpirationTime);
|
|
child = child.sibling;
|
|
}
|
|
}
|