mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Refactor React.Children to reduce indirection (#18332)
* Don't pool traversal context * Remove traverseAllChildrenImpl indirection All usages are internal so we can simply use the inner function directly. * Implement forEach through map * Remove second usage of traverseAllChildren This isn't useful by itself but makes the layering easier to follow. traverseAllChildren is only used at the lowest layer now. * Reimplement count() and toArray() in terms of map() * Inline the only use of mapSingleChildIntoContext * Move forEach down in the file * Use the language Get rid of the traversal context. Use closures. * Make mapIntoArray take an already escaped prefix * Move count state out of mapIntoArray * Inline traverseAllChildren into mapIntoArray * Inline handleChild into mapIntoArray
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* 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 traverseAllChildrenImpl(
|
||||
children,
|
||||
nameSoFar,
|
||||
callback,
|
||||
traverseContext,
|
||||
) {
|
||||
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 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) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user