React V16.8.6
- The test code
- The source code to explain
An element in React may have zero, one, or more direct child elements. The Children derived from React contains five methods for handling child elements.
map
similararray.map
forEach
similararray.forEach
count
similararray.length
toArray
only
Several important functions that React internally handles Children include
mapChildren
traverseAllChildrenImpl
mapIntoWithKeyPrefixInternal
mapSingleChildIntoContext
getPooledTraverseContext
releaseTraverseContext
Source is in the packages/react/SRC/ReactChildren. Js.
Exported statement
export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};
Copy the code
Children API
map
Similar to array.map, but with a few differences:
- The result must be a one-dimensional array, and the multi-dimensional array will be automatically flattened
- For each node returned, if
isValidElement(el) === true
, a key is added to it, and a new key is generated if the element already has a key
The first argument is the children to be iterated over, the second argument is the function to be iterated over, and the third argument is context, this when the function is iterated over.
If children == null, it returns directly.
mapChildren
/** * 分 析 'props. Children' ** @param {? *} children Children tree container. * @param {function(*, int)} func The map function. * @param {*} context Context for mapFunction. * @return {object} Object containing the ordered map of results. */
function mapChildren(children, func, context) {
if (children == null) {
return children;
}
// The iterated elements are thrown into result and returned
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
Copy the code
mapIntoWithKeyPrefixInternal
Children are fully traversed, and all nodes traversed are eventually stored in the array. Nodes that are ReactElement are added to the array after the key is changed.
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
// This is the key
let escapedPrefix = ' ';
if(prefix ! =null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
GetPooledTraverseContext and releaseTraverseContext are supporting functions
// Maintain a reuse pool of objects of size 10
// Each time an object from the pool is assigned, the attributes on the object are empty and thrown back to the pool
// The purpose of maintaining this pool is to improve performance, after all, the frequent creation and destruction of objects with many attributes consumes performance
const traverseContext = getPooledTraverseContext(
array, // result
escapedPrefix, / /"
func, // mapFunc
context, // context
);
// The most important sentence
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
releaseTraverseContext(traverseContext);
}
Copy the code
getPooledTraverseContext
GetPooledTraverseContext and releaseTraverseContext are used to maintain a pool of up to 10 objects. Children need to create objects frequently, which can cause performance problems, so maintain a fixed number of object pools, copy objects from the pool one at a time, and reset each property after use.
const POOL_SIZE = 10;
const traverseContextPool = [];
// Returns an object made of passed arguments
// If traverseContextPool length is 0, construct an object; otherwise, pop an object from traverseContextPool
// Assign the attributes of this object
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}; }}Copy the code
releaseTraverseContext
Add the object from getPooledTraverseContext to the array, regardless of the object pool >= 10
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); }}Copy the code
traverseAllChildren
There’s not much to say
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, ' ', callback, traverseContext);
}
Copy the code
traverseAllChildrenImpl
And what it does is it can be interpreted as
children
Is a renderable nodemapSingleChildIntoContext
Push children into the Result arraychildren
Is an array, and is called again for each element in the arraytraverseAllChildrenImpl
, the key passed in is the latest concatenated keychildren
If yes, passchildren[Symbol.iterator]
Gets the iterator of the objectiterator
, put the result of iteration intotraverseAllChildrenImpl
To deal with
The children () function is executed by iterating the children array into a single nodemapSingleChildIntoContext
.
This is a complicated function, and the function signature looks like this
children
Children to deal withnameSoFar
The parent key, which is passed layer by layer, is separated by:callback
If the current hierarchy is a renderable node,undefined
,boolean
Will becomenull
.string
,number
,? typeof
是REACT_ELEMENT_TYPE
orREACT_PORTAL_TYPE
To callmapSingleChildIntoContext
To deal withtraverseContext
An object from the object pool
/** * @param {? } children children tree container. 'children. Map' the first argument to be processed by children * @param {! string} nameSoFar Name of the key path so far. * @param {! Callback function} callback to invoke the with each child found. The callback when the map is * ` mapSingleChildIntoContext ` * @ param {? *} traverseContext Used to pass information throughout the traversal * process. An object in the object pool * @return {! number} The number of children in this subtree. */
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext,) {
// The core function of this function is to amortize the children array into a single node by traversing it
/ / and then to perform mapSingleChildIntoContext
// Start to judge the type of children
const type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
// Decide whether to call callback
// True if the node is renderable
let invokeCallback = false;
// If children === null and type is renderable, invokeCallback is true
if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.?typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true; }}}// If children are renderable nodes, call callback directly
/ / callback is mapSingleChildIntoContext
/ / let's go to read the mapSingleChildIntoContext function of the source code
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.
// const SEPARATOR = '.';
nameSoFar === ' ' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}
// Both nextName and nextNamePrefix handle key naming
let child;
let nextName;
let subtreeCount = 0; // Count of children found in the current subtree.
// const SUBSEPARATOR = ':';
const nextNamePrefix =
nameSoFar === ' ' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// If the node is an array, start iterating through the array and recurse each element in the array to traverseAllChildrenImpl
// This step is also used to flatten arrays
// React.Children.map(this.props.children, c => [[c, c]])
// c => [[c, c]] will be amortized to [c, c, c, c]
/ / if you look not to understand here will in mapSingleChildIntoContext certainly can see understand
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i); // .$dasdsa:
subtreeCount += traverseAllChildrenImpl(
child,
nextName, // The difference is that nameSoFar is changed, it will be in each layer of the concatenation, with: separationcallback, traverseContext, ); }}else {
// If not an array, see if children can support iteration
// use obj[symbol. iterator]
const iteratorFn = getIteratorFn(children);
/ /... There is a section in the middle of __DEV__ to check the use of correct code
// This is true only if the retrieved object is a function type
// Then execute the iterator, repeating the logic in if above
const iterator = iteratorFn.call(children);
let step;
let ii = 0;
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getComponentKey(child, ii++);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
}
return subtreeCount;
}
Copy the code
mapSingleChildIntoContext
Push child into the Result array of traverseContext. If child is ReactElement, change the key.
Called only if the passed Child is a renderable node. If performed mapFunc returns an array, the array will be in mapIntoWithKeyPrefixInternal continue processing.
/** * @param bookKeeping = 'traverseContext' * @param child = 'children' * @param childKey ' `nameSoFar` */
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} = bookKeeping; // traverseContext
// func is what we are doing in react.children.map (this.props. Children, c => c)
// the second function argument passed in
let mappedChild = func.call(context, child, bookKeeping.count++);
// Check whether the function returns an array
// Because that might happen
// React.Children.map(this.props.children, c => [c, c])
// In the case of c => [c, c], each child element is returned twice
Children (this.props. Children, c => [c, c]
// Return 4 child elements, c1 c1 C2 c2
if (Array.isArray(mappedChild)) {
// If it is an array, it goes back to the first function called
// Then go back to the previous problem of traverseAllChildrenImpl
// If c => [[c, c]], the return value should be [c, c].
// then [c, c] is passed as children
// traverseAllChildrenImpl The internal logic decides that the array will be recursively executed again
// So even if your function is c => [[[[c, c]]]]
[c, c, c, c]
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if(mappedChild ! =null) {
// It is not an array and the return value is not empty. Check whether the return value is a valid Element
// Clone the element and replace the key
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 childrenkeyPrefix + (mappedChild.key && (! child || child.key ! == mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) +'/'
: ' ') + childKey, ); } result.push(mappedChild); }}Copy the code
The map test
Map code is the above, write a demo to see how it works.
class Child extends Component {
render() {
console.log('this.props.children'.this.props.children)
const c = React.Children.map(this.props.children, c => {
debugger
return c
})
console.log('mappedChildren', c)
return <div>{c}</div>}}export default class Children extends Component {
render() {
// The code for return contains two cases: children are and are not arrays
return (
<Child>
<div>
childrendasddadas
<div>childrendasddadas</div>
<div>childrendasddadas</div>
</div>
<div key="key2">childrendasddadas</div>
<div key="key3">childrendasddadas</div>
{[
<div key="key4">childrendasddadas</div>.<div key="key5=">childrendasddadas</div>.<div key="key6:">childrendasddadas</div>]},</Child>)}}Copy the code
The printed result is as follows
The react.children. Map is used to amortize this. Props.Children and return a one-dimensional array of objects with keys. The generation of mappedChildren key is analyzed as follows.
This.props. Children itself is an array, and when traverseAllChildrenImpl is called for the first time, nextName is.0, and the first child executes traverseAllChildrenImpl, InvokeCallback to true, nameSoFar for 0, perform mapSingleChildIntoContext cloneAndReplaceKey went, The new key is generated as.0 (because (mappedchild.key && (! child || child.key ! == mappedChild.key) is false and keyPrefix is an empty string).
The second and third child’s keys are appended with.$, and in traverseAllChildrenImpl, NextName = nextNamePrefix + getComponentKey(child, I); NextNamePrefix is., I is 2, 3, getComponentKey is executed, because it has its own key, so it becomes. + $key2 =>.
function escape(key) {
const escapeRegex = /[=:]/g;
const escaperLookup = {
'=': '= 0'./ / replace =
':': '= 2'./ / replace:
};
const escapedString = (' ' + key).replace(escapeRegex, function(match) {
return escaperLookup[match];
});
return '$' + escapedString; // Return a string preceded by $
}
Copy the code
The fourth, fifth, and sixth elements are nested in the array, and this.props. Children traverses the array with an index of 3. TraverseAllChildrenImpl: nameSoFar; traverseAllChildrenImpl: nameSoFar; NextNamePrefix is.3:, calculated from the following sentence.
const nextNamePrefix = nameSoFar === ' ' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
Copy the code
$componentKey (I); $componentKey (I); $componentKey (I); $key5=0; The fifth and sixth = and: are treated to =0 and =2 by escape.
Call stack for debugger code:
Here is a map flow chart.
forEach
A similar array. ForEach.
The difference from Map is that the result parameter passed to getPooledTraverseContext is null, because forEach only iterates, not returns an array. And traverseAllChildren, its second argument becomes forEachSingleChild.
It is not as complex as map.
forEachChildren
Calling traverseAllChildren causes each child to be executed in forEachSingleChild
/** * Iterates through children that are typically specified as `props.children`. * The provided forEachFunc(child, index) will be called for each * leaf child. * * @param {? *} children Children tree container. `this.props.children` * @param {function(*, Int)} forEachFunc * @param {*} forEachContext Context for forEachContext. Iterate over the context of the function */
function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
const traverseContext = getPooledTraverseContext(
null.null,
forEachFunc,
forEachContext,
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}
Copy the code
forEachSingleChild
Execute each element in children in func
/** * Put each element in 'children' into 'func' to execute ** @param bookKeeping traverseContext * @param child single render child * @param */ is not used for name
function forEachSingleChild(bookKeeping, child, name) {
const {func, context} = bookKeeping;
func.call(context, child, bookKeeping.count++);
}
Copy the code
count
If you count the number of children, you’re counting the number of elements in the array that you’ve amortized
countChildren
TraverseAllChildren has a return value subtreeCount, which represents the number of children. After traverseAllChildren has traversed all the children, subtreeCount counts the result.
/** * Count the number of children, Number of elements * * number of elements * * number of elements * *} children Children tree container. * @return {number} The number of children. */
function countChildren(children) {
return traverseAllChildren(children, () => null.null);
}
Copy the code
toArray
MapChildren (children, child => child, context)
/** * = 'mapChildren' (children, child => child, 例 句 : Context) 'model * Flatten a child object (typically specified as' props. Children' appropriately re-keyed children. */
function toArray(children) {
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, child => child);
return result;
}
Copy the code
only
If the argument is a ReactElement, it is returned directly, otherwise an error is reported, and formal code is useless for testing.
/** * Returns the first child in a collection of children and verifies that there * is only one child in the collection. * The current implementation of this function assumes that a single child gets * passed without a wrapper, but the purpose of this helper function is to * abstract away the particular structure of children. * * @param {? object} children Child collection structure. * @return {ReactElement} The first and only `ReactElement` contained in the * structure. */
function onlyChild(children) {
invariant(
isValidElement(children),
'React.Children.only expected to receive a single React element child.',);return children;
}
function isValidElement(object) {
return (
typeof object === 'object'&& object ! = =null &&
object.?typeof === REACT_ELEMENT_TYPE
);
}
Copy the code
Among the exported functions, MAP is the most complex. After understanding the meaning and signature of each function, I have a deeper understanding of the whole. Look at the flow chart and the whole process becomes clear.
AD time