diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index 6e65fd8ddcde9..d2fce3f1ee3be 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -50,58 +50,29 @@ function escapeUserProvidedKey(text) { return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/'); } -const POOL_SIZE = 10; -const traverseContextPool = []; -function getPooledTraverseContext( - mapResult, - keyPrefix, - mapFunction, - mapContext, -) { - if (traverseContextPool.length) { - const traverseContext = traverseContextPool.pop(); - traverseContext.result = mapResult; - traverseContext.keyPrefix = keyPrefix; - traverseContext.func = mapFunction; - traverseContext.context = mapContext; - traverseContext.count = 0; - return traverseContext; - } else { - return { - result: mapResult, - keyPrefix: keyPrefix, - func: mapFunction, - context: mapContext, - count: 0, - }; - } -} - -function releaseTraverseContext(traverseContext) { - traverseContext.result = null; - traverseContext.keyPrefix = null; - traverseContext.func = null; - traverseContext.context = null; - traverseContext.count = 0; - if (traverseContextPool.length < POOL_SIZE) { - traverseContextPool.push(traverseContext); +/** + * Generate a key string that identifies a component within a set. + * + * @param {*} component A component that could contain a manual key. + * @param {number} index Index that is used if a manual key is not provided. + * @return {string} + */ +function getComponentKey(component, index) { + // Do some typechecking here since we call this blindly. We want to ensure + // that we don't block potential future ES APIs. + if ( + typeof component === 'object' && + component !== null && + component.key != null + ) { + // Explicit key + return escape(component.key); } + // Implicit key determined by the index in the set + return index.toString(36); } -/** - * @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, -) { +function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) { const type = typeof children; if (type === 'undefined' || type === 'boolean') { @@ -129,13 +100,33 @@ function traverseAllChildrenImpl( } if (invokeCallback) { - 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, - ); + const child = children; + let mappedChild = callback(child); + // 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: + let childKey = + nameSoFar === '' ? SEPARATOR + getComponentKey(child, 0) : nameSoFar; + if (Array.isArray(mappedChild)) { + let escapedChildKey = ''; + if (childKey != null) { + escapedChildKey = escapeUserProvidedKey(childKey) + '/'; + } + mapIntoArray(mappedChild, array, escapedChildKey, c => c); + } else if (mappedChild != null) { + if (isValidElement(mappedChild)) { + mappedChild = cloneAndReplaceKey( + mappedChild, + // Keep both the (mapped) and old keys if they differ, just as + // traverseAllChildren used to do for objects as children + escapedPrefix + + (mappedChild.key && (!child || child.key !== mappedChild.key) + ? escapeUserProvidedKey(mappedChild.key) + '/' + : '') + + childKey, + ); + } + array.push(mappedChild); + } return 1; } @@ -149,11 +140,12 @@ function traverseAllChildrenImpl( for (let i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); - subtreeCount += traverseAllChildrenImpl( + subtreeCount += mapIntoArray( child, + array, + escapedPrefix, nextName, callback, - traverseContext, ); } } else { @@ -188,11 +180,12 @@ function traverseAllChildrenImpl( while (!(step = iterator.next()).done) { child = step.value; nextName = nextNamePrefix + getComponentKey(child, ii++); - subtreeCount += traverseAllChildrenImpl( + subtreeCount += mapIntoArray( child, + array, + escapedPrefix, nextName, callback, - traverseContext, ); } } else if (type === 'object') { @@ -218,121 +211,6 @@ function traverseAllChildrenImpl( return subtreeCount; } -/** - * Traverses children that are typically specified as `props.children`, but - * might also be specified through attributes: - * - * - `traverseAllChildren(this.props.children, ...)` - * - `traverseAllChildren(this.props.leftPanelChildren, ...)` - * - * The `traverseContext` is an optional argument that is passed through the - * entire traversal. It can be used to store accumulations or anything else that - * the callback might find relevant. - * - * @param {?*} children Children tree object. - * @param {!function} callback To invoke upon traversing each child. - * @param {?*} traverseContext Context for traversal. - * @return {!number} The number of children in this subtree. - */ -function traverseAllChildren(children, callback, traverseContext) { - if (children == null) { - return 0; - } - - return traverseAllChildrenImpl(children, '', callback, traverseContext); -} - -/** - * Generate a key string that identifies a component within a set. - * - * @param {*} component A component that could contain a manual key. - * @param {number} index Index that is used if a manual key is not provided. - * @return {string} - */ -function getComponentKey(component, index) { - // Do some typechecking here since we call this blindly. We want to ensure - // that we don't block potential future ES APIs. - if ( - typeof component === 'object' && - component !== null && - component.key != null - ) { - // Explicit key - return escape(component.key); - } - // Implicit key determined by the index in the set - return index.toString(36); -} - -function forEachSingleChild(bookKeeping, child, name) { - const {func, context} = bookKeeping; - func.call(context, child, bookKeeping.count++); -} - -/** - * Iterates through children that are typically specified as `props.children`. - * - * See https://reactjs.org/docs/react-api.html#reactchildrenforeach - * - * The provided forEachFunc(child, index) will be called for each - * leaf child. - * - * @param {?*} children Children tree container. - * @param {function(*, int)} forEachFunc - * @param {*} forEachContext Context for forEachContext. - */ -function forEachChildren(children, forEachFunc, forEachContext) { - if (children == null) { - return children; - } - const traverseContext = getPooledTraverseContext( - null, - null, - forEachFunc, - forEachContext, - ); - traverseAllChildren(children, forEachSingleChild, traverseContext); - releaseTraverseContext(traverseContext); -} - -function mapSingleChildIntoContext(bookKeeping, child, childKey) { - const {result, keyPrefix, func, context} = bookKeeping; - - let mappedChild = func.call(context, child, bookKeeping.count++); - if (Array.isArray(mappedChild)) { - mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); - } else if (mappedChild != null) { - if (isValidElement(mappedChild)) { - mappedChild = cloneAndReplaceKey( - mappedChild, - // Keep both the (mapped) and old keys if they differ, just as - // traverseAllChildren used to do for objects as children - keyPrefix + - (mappedChild.key && (!child || child.key !== mappedChild.key) - ? escapeUserProvidedKey(mappedChild.key) + '/' - : '') + - childKey, - ); - } - result.push(mappedChild); - } -} - -function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { - let escapedPrefix = ''; - if (prefix != null) { - escapedPrefix = escapeUserProvidedKey(prefix) + '/'; - } - const traverseContext = getPooledTraverseContext( - array, - escapedPrefix, - func, - context, - ); - traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); - releaseTraverseContext(traverseContext); -} - /** * Maps children that are typically specified as `props.children`. * @@ -351,7 +229,17 @@ function mapChildren(children, func, context) { return children; } const result = []; - mapIntoWithKeyPrefixInternal(children, result, null, func, context); + let count = 0; + mapIntoArray( + children, + result, + '', + '', + function(child) { + return func.call(context, child, count++); + }, + context, + ); return result; } @@ -365,7 +253,32 @@ function mapChildren(children, func, context) { * @return {number} The number of children. */ function countChildren(children) { - return traverseAllChildren(children, () => null, null); + let n = 0; + mapChildren(children, () => n++); + return n; +} + +/** + * Iterates through children that are typically specified as `props.children`. + * + * See https://reactjs.org/docs/react-api.html#reactchildrenforeach + * + * The provided forEachFunc(child, index) will be called for each + * leaf child. + * + * @param {?*} children Children tree container. + * @param {function(*, int)} forEachFunc + * @param {*} forEachContext Context for forEachContext. + */ +function forEachChildren(children, forEachFunc, forEachContext) { + mapChildren( + children, + function() { + forEachFunc.apply(this, arguments); + // Don't return anything. + }, + forEachContext, + ); } /** @@ -375,9 +288,7 @@ function countChildren(children) { * See https://reactjs.org/docs/react-api.html#reactchildrentoarray */ function toArray(children) { - const result = []; - mapIntoWithKeyPrefixInternal(children, result, null, child => child); - return result; + return mapChildren(children, child => child) || []; } /**