/** * Ensure that every element either is passed in a static location, in an * array with an explicit keys property defined, or in an object literal * with valid key property. * * @internal * @param {ReactNode} node Statically passed child of any type. * @param {*} parentType node's parent's type. */ function validateChildKeys(node, parentType) { if (typeof node !== 'object') { return; } if (Array.isArray(node)) { for (var i = 0; i < node.length; i++) { var child = node[i]; if (ReactElement.isValidElement(child)) { validateExplicitKey(child, parentType); } } } else if (ReactElement.isValidElement(node)) { // This element was passed in a valid location. if (node._store) { node._store.validated = true; } } else if (node) { var iteratorFn = getIteratorFn(node); // Entry iterators provide implicit keys. if (iteratorFn) { if (iteratorFn !== node.entries) { var iterator = iteratorFn.call(node); var step; while (!(step = iterator.next()).done) { if (ReactElement.isValidElement(step.value)) { validateExplicitKey(step.value, parentType); } } } } } }
/** * Ensure that every element either is passed in a static location, in an * array with an explicit keys property defined, or in an object literal * with valid key property. * * @internal * @param {ReactNode} node Statically passed child of any type. * @param {*} parentType node's parent's type. */ function validateChildKeys(node, parentType) { if (Array.isArray(node)) { for (var i = 0; i < node.length; i++) { var child = node[i]; if (ReactElement.isValidElement(child)) { validateExplicitKey(child, parentType); } } } else if (ReactElement.isValidElement(node)) { // This element was passed in a valid location. node._store.validated = true; } else if (node) { var iteratorFn = getIteratorFn(node); // Entry iterators provide implicit keys. if (iteratorFn && iteratorFn !== node.entries) { var iterator = iteratorFn.call(node); var step; while (!(step = iterator.next()).done) { if (ReactElement.isValidElement(step.value)) { validateExplicitKey(step.value, parentType); } } } else if (typeof node === 'object') { monitorUseOfObjectMap(); for (var key in node) { if (node.hasOwnProperty(key)) { validatePropertyKey(key, node[key], parentType); } } } } }
function updateSlot( returnFiber : Fiber, oldFiber : ?Fiber, newChild : any, priority : PriorityLevel ) : ?Fiber { // Update the fiber if the keys match, otherwise return null. const key = oldFiber ? oldFiber.key : null; if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn'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, priority); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { return updateElement(returnFiber, oldFiber, newChild, priority); } else { return null; } } case REACT_COROUTINE_TYPE: { if (newChild.key === key) { return updateCoroutine(returnFiber, oldFiber, newChild, priority); } else { return null; } } case REACT_YIELD_TYPE: { if (newChild.key === key) { return updateYield(returnFiber, oldFiber, newChild, priority); } else { return null; } } } if (isArray(newChild) || getIteratorFn(newChild)) { // Fragments doesn't have keys so if the previous key is implicit we can // update it. if (key !== null) { return null; } return updateFragment(returnFiber, oldFiber, newChild, priority); } } return null; }
function updateFromMap( existingChildren : Map<string | number, Fiber>, returnFiber : Fiber, newIdx : number, newChild : any, priority : PriorityLevel ) : Fiber | null { if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn'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, priority); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key ) || null; return updateElement(returnFiber, matchedFiber, newChild, priority); } case REACT_COROUTINE_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key ) || null; return updateCoroutine(returnFiber, matchedFiber, newChild, priority); } case REACT_YIELD_TYPE: { // Yields doesn't have keys, so we neither have to check the old nor // new node for the key. If both are yields, they match. const matchedFiber = existingChildren.get(newIdx) || null; return updateYield(returnFiber, matchedFiber, newChild, priority); } case REACT_PORTAL_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key ) || null; return updatePortal(returnFiber, matchedFiber, newChild, priority); } } if (isArray(newChild) || getIteratorFn(newChild)) { const matchedFiber = existingChildren.get(newIdx) || null; return updateFragment(returnFiber, matchedFiber, newChild, priority); } throwOnInvalidObjectType(returnFiber, newChild); } return null; }
function createChild( returnFiber : Fiber, newChild : any, priority : PriorityLevel ) : Fiber | null { if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn'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, priority); created.return = returnFiber; return created; } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { const created = createFiberFromElement(newChild, priority); created.ref = coerceRef(null, newChild); created.return = returnFiber; return created; } case REACT_COROUTINE_TYPE: { const created = createFiberFromCoroutine(newChild, priority); created.return = returnFiber; return created; } case REACT_YIELD_TYPE: { const created = createFiberFromYield(newChild, priority); created.type = newChild.value; created.return = returnFiber; return created; } case REACT_PORTAL_TYPE: { const created = createFiberFromPortal(newChild, priority); created.return = returnFiber; return created; } } if (isArray(newChild) || getIteratorFn(newChild)) { const created = createFiberFromFragment(newChild, priority); created.return = returnFiber; return created; } throwOnInvalidObjectType(returnFiber, newChild); } return null; }
function isNode(propValue) { switch (typeof propValue) { case 'number': case 'string': case 'undefined': return true; case 'boolean': return !propValue; case 'object': if (Array.isArray(propValue)) { return propValue.every(isNode); } if (propValue === null || ReactElement.isValidElement(propValue)) { return true; } var iteratorFn = getIteratorFn(propValue); if (iteratorFn) { var iterator = iteratorFn.call(propValue); var step; if (iteratorFn !== propValue.entries) { while (!(step = iterator.next()).done) { if (!isNode(step.value)) { return false; } } } else { // Iterator will provide entry [k,v] tuples rather than values. while (!(step = iterator.next()).done) { var entry = step.value; if (entry) { if (!isNode(entry[1])) { return false; } } } } } else { propValue = ReactFragment.extractIfFragment(propValue); for (var k in propValue) { if (!isNode(propValue[k])) { return false; } } } return true; default: return false; } }
/** * @param {?*} children Children tree container. * @param {!string} nameSoFar Name of the key path so far. * @param {!function} callback Callback to invoke with each child found. * @param {?*} traverseContext Used to pass information throughout the traversal * process. * @return {!number} The number of children in this subtree. */ function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext ) { var type = typeof children; if (type === 'undefined' || type === 'boolean') { // All of the above are perceived as null. children = null; } if (children === null || type === 'string' || type === 'number' || ReactElement.isValidElement(children)) { callback( traverseContext, children, // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar ); return 1; } var child; var nextName; var subtreeCount = 0; // Count of children found in the current subtree. if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + getComponentKey(child, i) ); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { var iteratorFn = getIteratorFn(children); if (iteratorFn) { var iterator = iteratorFn.call(children); var step; if (iteratorFn !== children.entries) { var ii = 0; while (!(step = iterator.next()).done) { child = step.value; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + getComponentKey(child, ii++) ); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { if (__DEV__) { warning( didWarnAboutMaps, 'Using Maps as children is not yet fully supported. It is an ' + 'experimental feature that might be removed. Convert it to a ' + 'sequence / iterable of keyed ReactElements instead.' ); didWarnAboutMaps = true; } // Iterator will provide entry [k,v] tuples rather than values. while (!(step = iterator.next()).done) { var entry = step.value; if (entry) { child = entry[1]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + wrapUserProvidedKey(entry[0]) + SUBSEPARATOR + getComponentKey(child, 0) ); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } } } else if (type === 'object') { invariant( children.nodeType !== 1, 'traverseAllChildren(...): Encountered an invalid child; DOM ' + 'elements are not valid children of React components.' ); var fragment = ReactFragment.extract(children); for (var key in fragment) { if (fragment.hasOwnProperty(key)) { child = fragment[key]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + wrapUserProvidedKey(key) + SUBSEPARATOR + getComponentKey(child, 0) ); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } } } return subtreeCount; }
/** * @param {?*} children Children tree container. * @param {!string} nameSoFar Name of the key path so far. * @param {!number} indexSoFar Number of children encountered until this point. * @param {!function} callback Callback to invoke with each child found. * @param {?*} traverseContext Used to pass information throughout the traversal * process. * @return {!number} The number of children in this subtree. */ function traverseAllChildrenImpl( children, nameSoFar, indexSoFar, callback, traverseContext ) { var type = typeof children; if (type === 'undefined' || type === 'boolean') { // All of the above are perceived as null. children = null; } if (children === null || type === 'string' || type === 'number' || ReactElement.isValidElement(children)) { callback( traverseContext, children, // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, indexSoFar ); return 1; } var child, nextName, nextIndex; var subtreeCount = 0; // Count of children found in the current subtree. if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + getComponentKey(child, i) ); nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, nextName, nextIndex, callback, traverseContext ); } } else { var iteratorFn = getIteratorFn(children); if (iteratorFn) { var iterator = iteratorFn.call(children); var step; if (iteratorFn !== children.entries) { var ii = 0; while (!(step = iterator.next()).done) { child = step.value; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + getComponentKey(child, ii++) ); nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, nextName, nextIndex, callback, traverseContext ); } } else { // Iterator will provide entry [k,v] tuples rather than values. while (!(step = iterator.next()).done) { var entry = step.value; if (entry) { child = entry[1]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + wrapUserProvidedKey(entry[0]) + SUBSEPARATOR + getComponentKey(child, 0) ); nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, nextName, nextIndex, callback, traverseContext ); } } } } else if (type === 'object') { invariant( children.nodeType !== 1, 'traverseAllChildren(...): Encountered an invalid child; DOM ' + 'elements are not valid children of React components.' ); for (var key in children) { if (children.hasOwnProperty(key)) { child = children[key]; nextName = ( (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) + wrapUserProvidedKey(key) + SUBSEPARATOR + getComponentKey(child, 0) ); nextIndex = indexSoFar + subtreeCount; subtreeCount += traverseAllChildrenImpl( child, nextName, nextIndex, callback, traverseContext ); } } } } return subtreeCount; }
function reconcileChildrenIterator( returnFiber : Fiber, currentFirstChild : ?Fiber, newChildrenIterable : Iterable<*>, priority : PriorityLevel) : ?Fiber { // This is the same implementation as reconcileChildrenArray(), // but using the iterator instead. const iteratorFn = getIteratorFn(newChildrenIterable); if (typeof iteratorFn !== 'function') { throw new Error('An object is not an iterable.'); } if (__DEV__) { // First, validate keys. // We'll get a different iterator later for the main pass. const newChildren = iteratorFn.call(newChildrenIterable); if (newChildren == null) { throw new Error('An iterable object provided no iterator.'); } let knownKeys = null; let step = newChildren.next(); for (; !step.done; step = newChildren.next()) { const child = step.value; knownKeys = warnOnDuplicateKey(child, knownKeys); } } const newChildren = iteratorFn.call(newChildrenIterable); if (newChildren == null) { throw new Error('An iterable object provided no iterator.'); } let resultingFirstChild : ?Fiber = null; let previousNewFiber : ?Fiber = null; let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; let nextOldFiber = null; let step = newChildren.next(); for (; oldFiber && !step.done; newIdx++, step = newChildren.next()) { if (oldFiber) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } } const newFiber = updateSlot( returnFiber, oldFiber, step.value, priority ); if (!newFiber) { // 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) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && !newFiber.alternate) { // 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) { // 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) { // 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, priority ); if (!newFiber) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (!previousNewFiber) { // 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, priority ); if (newFiber) { if (shouldTrackSideEffects) { if (newFiber.alternate) { // 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) { 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; }
// 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, newChild : any, priority : PriorityLevel ) : ?Fiber { // 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. if (ReactFeatureFlags.disableNewFiberFeatures) { // Support only the subset of return types that Stack supports. Treat // everything else as empty, but log a warning. if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement( returnFiber, currentFirstChild, newChild, priority )); case REACT_PORTAL_TYPE: return placeSingleChild(reconcileSinglePortal( returnFiber, currentFirstChild, newChild, priority )); } } if (returnFiber.tag === HostRoot) { // Top-level only accepts elements or portals invariant( // If the root has an error effect, this is an intentional unmount. // Don't throw an error. returnFiber.effectTag & Err, 'render(): Invalid component element.' ); } else { 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 FunctionalComponent: { // Composites accept elements, portals, null, or false const Component = returnFiber.type; invariant( newChild === null || newChild === false, '%s.render(): A valid React element (or null) must be ' + 'returned. You may have returned undefined, an array or some ' + 'other invalid object.', Component.displayName || Component.name || 'Component' ); } } } return deleteRemainingChildren(returnFiber, currentFirstChild); } if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild(reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, priority )); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement( returnFiber, currentFirstChild, newChild, priority )); case REACT_COROUTINE_TYPE: return placeSingleChild(reconcileSingleCoroutine( returnFiber, currentFirstChild, newChild, priority )); case REACT_YIELD_TYPE: return placeSingleChild(reconcileSingleYield( returnFiber, currentFirstChild, newChild, priority )); case REACT_PORTAL_TYPE: return placeSingleChild(reconcileSinglePortal( returnFiber, currentFirstChild, newChild, priority )); } if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, priority ); } if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, priority ); } } if (typeof newChild === 'undefined') { switch (returnFiber.tag) { case HostRoot: // TODO: Top-level render break; 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 FunctionalComponent: { 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); }
// 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, newChild : any, priority : PriorityLevel ) : ?Fiber { // 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. if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild(reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, priority )); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement( returnFiber, currentFirstChild, newChild, priority )); case REACT_COROUTINE_TYPE: return placeSingleChild(reconcileSingleCoroutine( returnFiber, currentFirstChild, newChild, priority )); case REACT_YIELD_TYPE: return placeSingleChild(reconcileSingleYield( returnFiber, currentFirstChild, newChild, priority )); case REACT_PORTAL_TYPE: return placeSingleChild(reconcileSinglePortal( returnFiber, currentFirstChild, newChild, priority )); } if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, priority ); } if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, priority ); } } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); }
/** * @param {?*} children Children tree container. * @param {!string} nameSoFar Name of the key path so far. * @param {!function} callback Callback to invoke with each child found. * @param {?*} traverseContext Used to pass information throughout the traversal * process. * @return {!number} The number of children in this subtree. */ function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext ) { var type = typeof children; if (type === 'undefined' || type === 'boolean') { // All of the above are perceived as null. children = null; } if (children === null || type === 'string' || type === 'number' || ReactElement.isValidElement(children)) { callback( traverseContext, children, // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar ); return 1; } var child; var nextName; var subtreeCount = 0; // Count of children found in the current subtree. var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { var iteratorFn = getIteratorFn(children); if (iteratorFn) { var iterator = iteratorFn.call(children); var step; if (iteratorFn !== children.entries) { var ii = 0; while (!(step = iterator.next()).done) { child = step.value; nextName = nextNamePrefix + getComponentKey(child, ii++); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { if (__DEV__) { warning( didWarnAboutMaps, 'Using Maps as children is not yet fully supported. It is an ' + 'experimental feature that might be removed. Convert it to a ' + 'sequence / iterable of keyed ReactElements instead.' ); didWarnAboutMaps = true; } // Iterator will provide entry [k,v] tuples rather than values. while (!(step = iterator.next()).done) { var entry = step.value; if (entry) { child = entry[1]; nextName = ( nextNamePrefix + wrapUserProvidedKey(entry[0]) + SUBSEPARATOR + getComponentKey(child, 0) ); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } } } else if (type === 'object') { var addendum = ''; if (__DEV__) { addendum = ' If you meant to render a collection of children, use an array ' + 'instead or wrap the object using createFragment(object) from the ' + 'React add-ons.'; if (children._isReactElement) { addendum = ' It looks like you\'re using an element created by a different ' + 'version of React. Make sure to use only one copy of React.'; } if (ReactCurrentOwner.current) { var name = ReactCurrentOwner.current.getName(); if (name) { addendum += ' Check the render method of `' + name + '`.'; } } } var childrenString = String(children); invariant( false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum ); } } return subtreeCount; }
function reconcileChildrenIterator( returnFiber : Fiber, currentFirstChild : Fiber | null, newChildrenIterable : Iterable<*>, priority : PriorityLevel) : 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__) { // Warn about using Maps as children if (typeof newChildrenIterable.entries === 'function') { const possibleMap = (newChildrenIterable : any); if (possibleMap.entries === iteratorFn) { let mapsAsChildrenAddendum = ''; const owner = ReactCurrentOwner.owner || returnFiber._debugOwner; if (owner && typeof owner.tag === 'number') { const mapsAsChildrenOwnerName = getComponentName((owner : any)); if (mapsAsChildrenOwnerName) { mapsAsChildrenAddendum = '\n\nCheck the render method of `' + mapsAsChildrenOwnerName + '`.'; } } warning( didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', mapsAsChildrenAddendum ); 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 = warnOnDuplicateKey(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, priority ); 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) { 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, priority ); 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, priority ); 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; }
/** * @param {?*} children Children tree container. * @param {!string} nameSoFar Name of the key path so far. * @param {!function} callback Callback to invoke with each child found. * @param {?*} traverseContext Used to pass information throughout the traversal * process. * @return {!number} The number of children in this subtree. */ function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext ) { var type = typeof children; if (type === 'undefined' || type === 'boolean') { // All of the above are perceived as null. children = null; } if (children === null || type === 'string' || type === 'number' || // The following is inlined from ReactElement. This means we can optimize // some checks. React Fiber also inlines this logic for similar purposes. (type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)) { callback( traverseContext, children, // If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar ); return 1; } var child; var nextName; var subtreeCount = 0; // Count of children found in the current subtree. var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else { var iteratorFn = getIteratorFn(children); if (iteratorFn) { if (__DEV__) { // Warn about using Maps as children if (iteratorFn === children.entries) { let mapsAsChildrenAddendum = ''; if (ReactCurrentOwner.current) { var mapsAsChildrenOwnerName = ReactCurrentOwner.current.getName(); if (mapsAsChildrenOwnerName) { mapsAsChildrenAddendum = '\n\nCheck the render method of `' + mapsAsChildrenOwnerName + '`.'; } } warning( didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', mapsAsChildrenAddendum ); didWarnAboutMaps = true; } } var iterator = iteratorFn.call(children); var step; var ii = 0; while (!(step = iterator.next()).done) { child = step.value; nextName = nextNamePrefix + getComponentKey(child, ii++); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext ); } } else if (type === 'object') { var addendum = ''; if (__DEV__) { addendum = ' If you meant to render a collection of children, use an array ' + 'instead or wrap the object using createFragment(object) from the ' + 'React add-ons.'; if (ReactCurrentOwner.current) { var name = ReactCurrentOwner.current.getName(); if (name) { addendum += '\n\nCheck the render method of `' + name + '`.'; } } } var childrenString = String(children); invariant( false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum ); } } return subtreeCount; }