One example,
function ChildrenDemo(props) {
console.log(props.children, 'children30');
console.log(React.Children.map(props.children, item => [item, [item, [item]]]), 'children31');
// console.log(React.Children.map(props.children,item=>item),'children31')
return props.children;
}
export default() = > (
<ChildrenDemo>
<span key={'. 0/ '} >1</span>
<span>2</span>
</ChildrenDemo>
)
Copy the code
Props. Children:
React.children. Map (props. Children, item => [item, [item, [item]]] :
[item, [item, [item]] [item, [item]] [item,item,item]] [item,item,item]
Second, the React. Children. The map () function: zh-hans.reactjs.org/docs/react-…
Source:
// React.Children.map(props.children,item=>[item,[item,] ])
function mapChildren(children, func, context) {
if (children == null) {
return children;
}
const result = [];
// After basic judgment and initialization, call this method
//props.children,[],null,(item)=>{return [item,[item,] ]},undefined
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
export {
//as is the name of mapChildren
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};
Copy the code
If the array is rolled around, return the result
Third, mapIntoWithKeyPrefixInternal () function: getPooledTraverseContext ()/traverseAllChildren ()/releaseTraverseContext () wrapper
Source:
//First time: props. Children, [],null , (item)= >{return [item,[item,] ]} , undefined
//[item,[item,]],[],. 0 , c => c , undefined
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
let escapedPrefix = ' ';
//If there are more than one consecutive string/ is followed by a slash after the matching string
if(prefix ! =null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
//Find an object from the pool
//[].' '.(item)= >{return [item,[item,] ]},undefined
//traverseContext=
// {
// result:[],
// keyPrefix:' '.
// func:(item)= >{return [item,[item,] ]},
// context:undefined.
// count:0.
// }
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
//Flatten a nested set of numbers
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
releaseTraverseContext(traverseContext);
}
Copy the code
Resolution:
① The escapeUserProvidedKey() function is usually used for second-level recursion
What it does: Add a/after /
Source:
const userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text) {
// If there are consecutive slashes in the string, the slash is followed by the matching string
return (' ' + text).replace(userProvidedKeyEscapeRegex, '$& /');
}
Copy the code
React defines a rule for keys: if there are consecutive slashes in a string, add a slash after the matching string
Ex. :
let a='aa/a/'
console.log(a.replace(/\/+/g.'$& /')); // aa//a//
Copy the code
2.getPooledTraverseContext()
What it does: Creates a pool of objects and reuses objects, thereby reducing many of the memory and garbage collection costs associated with Object creation
Source:
// The maximum capacity of the object pool is 10
const POOL_SIZE = 10;
/ / object pool
const traverseContextPool = [];
//[],'',(item)=>{return [item,[item,] ]},undefined
function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
// If there are objects in the object pool, an object will be queued.
// Assign arguments' value to the object property
// Finally return the object
if (traverseContextPool.length) {
const traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
}
// If not, a new object is returned
else {
/ / {
// result:[],
// keyPrefix:'',
// func:(item)=>{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0.
};
}
}
Copy the code
Resolution: The traverseContext object is used for each recursion of a map(). The purpose of the traverseContextPool object pool is to reuse the objects in it to reduce memory consumption and initialize the reused objects at the end of the map(). And push it into the object pool (releaseTraverseContext) for the next map()
(3) mapSingleChildIntoContext () is mapSingleChildIntoContext traverseAllChildren (children, mapSingleChildIntoContext. The second parameter of traverseContext, in order to avoid talking about traverseAllChildren and looking at this API, let’s analyze it first
The recursion is still the child of the array; Add a single ReactElement child to Result
Source:
//bookKeeping:traverseContext=
/ / {
// result:[],
// keyPrefix:'',
// func:(item)=>{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
//child:<span>1<span/>
//childKey:.0
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
// Destruct the assignment
const {result, keyPrefix, func, context} = bookKeeping;
//func:(item)=>{return [item,[item,] ]},
/ / the item that is < span > 1 < / span >
// the second argument, bookKeeping. Count++, is interesting and doesn't use it at all, but still counts
let mappedChild = func.call(context, child, bookKeeping.count++);
// If the react.children.map () second argument callback is still an array,
/ / recursive call mapIntoWithKeyPrefixInternal, continue the previous steps,
// until a single ReactElement
if (Array.isArray(mappedChild)) {
//mappedChild:[item,[item,] ]
//result:[]
//childKey:.0
//func:c => c
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
}
// mappedChild is a single ReactElement and is not null
else if(mappedChild ! =null) {
if (isValidElement(mappedChild)) {
// Assign the same attribute to the new object except for key, replacing the key attribute
mappedChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
// If the old and new keys are different, keep both, as traverseAllChildren do with Objects
keyPrefix +
(mappedChild.key && (! child || child.key ! == mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: ' ') +
childKey,
);
}
//result is the result of the map return
result.push(mappedChild);
}
}
Copy the code
(1) Let child call the func method and continue recursing if the result is an array. If it is a single ReactElement, place it in the Result array
(2) cloneAndReplaceKey() assigns the same attribute to the new object as its name, replacing the key attribute
Take a quick look at the source code:
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
Copy the code
(3) isValidElement() check whether it is ReactElement.
export function isValidElement(object) {
return (
typeof object= = ='object' &&
object! = =null &&
object.?typeof === REACT_ELEMENT_TYPE
);
}
Copy the code
(4)traverseAllChildren()
Action: traverseAllChildrenImpl Trigger
Source:
// children, mapSingleChildIntoContext, traverseContext
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, ' ', callback, traverseContext);
}
Copy the code
5.traverseAllChildrenImpl()
What it does: A core recursive function that flattens a nested array
Source:
// children, '', mapSingleChildIntoContext, traverseContext
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
//traverseContext=
// {
// result:[],
// keyPrefix:' '.
// func:(item) = >{return [item,[item,] ]},
// context:undefined,
// count:0,
// }
traverseContext,
) {
const type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are considered null
// All of the above are perceived as null.
children = null;
}
// Call func flag
let invokeCallback = false;
if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
/ / if props. The children is a single ReactElement/PortalElement
When traverseAllChildrenImpl, 1 and 2 are child
// Must trigger invokeCallback=true
switch (children.?typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
if (invokeCallback) {
callback(
traverseContext,
children,
// If there is only one child node, put it in an array
// 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.
/ / $= 0
//<span>1<span/> key='.0'
nameSoFar === ' ' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}
let child;
let nextName;
// How many children are there
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
/ /.
nameSoFar === ' ' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
//<span>1</span>
child = children[i];
// If the key is not set manually, the first layer is.0 and the second layer is.1
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else {
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
if (__DEV__) {
// Warn about using Maps as children
if (iteratorFn === children.entries) {
warning(
didWarnAboutMaps,
'Using Maps as children is unsupported and will likely yield ' +
'unexpected results. Convert it to a sequence/iterable of keyed ' +
'ReactElements instead.'.
);
didWarnAboutMaps = true;
}
}
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,
);
}
}
Throw Error if it is a pure object
else if (type === 'object') {
let addendum = ' ';
if (__DEV__) {
addendum =
' If you meant to render a collection of children, use an array ' +
'instead.' +
ReactDebugCurrentFrame.getStackAddendum();
}
const childrenString = ' ' + 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;
}
Copy the code
(1) Are children objects? Typeof is REACT_ELEMENT_TYPE/REACT_PORTAL_TYPE
Call the callback mapSingleChildIntoContext, copy in addition to the key attributes, replace the key attribute, puts it into the result
(2) Children is an Array of children and traverseAllChildrenImpl
Iii. Flow chart
4. Write an interview question based on the react.children. Map () algorithm
Array flattening: Implement a flatten method that outputs a flattened array if you input an array of elements
// Example
let givenArr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, 11, 12, [12, 13, [14]]]], 10];
let outputArr = [1.2.2.3.4.5.5.6.7.8.9.11.12.12.13.14.10]
Flatten (givenArr) -- >outputArr
Copy the code
Solution 1: Use recursion according to the flow chart above
function flatten(arr){
var res = [];
for(var i=0; i<arr.length; i++){
if(Array.isArray(arr[i])){
res = res.concat(flatten(arr[i]));
}else{
res.push(arr[i]);
}
}
return res;
}
Copy the code
Solution 2: ES6
function flatten(array) {
// Merge as long as the elements in the array have a nested array
while(array.some(item=>Array.isArray(item)))
array=[].concat(...array)
console.log(array) / /,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10 [1]
return array
}
Copy the code
(after)