preface
Last article covered some of the basic API of React. Today’s article covers React. Children
React.children
In React. Js, select Children, which lists several methods of React
const React = {
Children: {
map.
forEach,
count,
toArray,
only,
},
}
Copy the code
- map
- forEach
- count
- toArray
- only
Use of several methods
There’s a code that looks like this
import React from 'react';
function ChildrenDemo(props) {
console.log(props.children, 'props.children');
console.log(React.Children.map(props.children, item => item), 'map');
console.log(React.Children.map(props.children, item => [item, [item, item]]), 'map');
console.log(React.Children.forEach(props.children, item => item), 'forEach');
console.log(React.Children.forEach(props.children, item => [item, [item, item]]), 'forEach');
console.log(React.Children.toArray(props.children), 'toArray');
console.log(React.Children.count(props.children), 'count');
console.log(React.Children.only(props.children[0]), 'only');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
Let’s take a look at the console output:
We see
- Map method: Depending on the function passing the parameters, the output result is an array containing the information of each node
- ForEach: returns undefined no matter what argument is passed
- The toArray method returns an array containing information about each node
- The count method returns a number that is the number of nodes
- Only method: We pass a node to the only method and return the node information
So there’s definitely a question in your mind, why are these results returned? Now, let’s analyze the source code to get the answer we want
Source code analysis
We analyze the source code step by step through breakpoints, and draw a flow chart at the end to deepen our understanding
React.development.js (react.development.js)
import React from './react.development.js';
Copy the code
Map method (item => item)
import React from './react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.map(props.children, item => item), 'map');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
In react.development.js, find all the functions for the map method, put breakpoints where you need them, and see how it works.
With breakpoints, the map method first executes the mapChildren function
function mapChildren(children, func, context) {
// Check whether children passed in is null
if (children == null) {
// is null, returns children directly
return children;
}
// not null, define a result
var result = [];
// Call the function and pass in the corresponding five arguments
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
/ / return the result
return result;
}
Copy the code
- Call mapIntoWithKeyPrefixInternal method
// The method takes five arguments
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context){
// Define escapedPrefix for passing parameters later
var escapedPrefix = ' ';
// Check that the passed prefix argument is not null
if(prefix ! =null) {
// If prefix is not empty, call the escapeUserProvidedKey method, pass in prefix, and add '/' to the result obtained.
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
// Call the getPooledTraverseContext method, passing in four arguments, and assigning the result to traverseContext to facilitate the following function parameters
var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);
/ / call traverseAllChildren method, was introduced into three parameters, including mapSingleChildIntoContext is a function that is the role of the function of nested array
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
// Call the releaseTraverseContext method, passing in the parameters
releaseTraverseContext(traverseContext);
}
Copy the code
- The escapeUserProvidedKey method is called
const userProvidedKeyEscapeRegex = /\/+/g;
// Match consecutive '\' and replace with '$&/'
function escapeUserProvidedKey(text) {
return (' ' + text).replace(userProvidedKeyEscapeRegex, '$& /');
}
Copy the code
- Next, analyze the getPooledTraverseContext method called
The main purpose of this method is to create a pool of objects and reuse objects, thereby reducing the memory footprint and gc (garbage collection) consumption associated with many Object creation
// a buffer pool of size 10 is defined here
const POOL_SIZE = 10;
var traverseContextPool = [];
function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) {
if (traverseContextPool.length) { // If there is a value in the buffer pool, a value is extracted and used
var traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
} else { // If there is no value in the buffer pool, the passed argument is directly assigned and an object is returned
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0
};
}
}
Copy the code
In both cases, the return value is the variable value received when the function is called.
- After the traverseContext value is reached, the traverseAllChildren method is called
/ / function is introduced into the three parameters, when the first parameter to the children, the second parameter is a mapSingleChildIntoContext function, the third argument when we pass the method returns the value of the above
function traverseAllChildren(children, callback, traverseContext) {
// If the child node is empty, return 0
if (children == null) {
return 0;
}
// Otherwise call traverseAllChildrenImpl
return traverseAllChildrenImpl(children, ' ', callback, traverseContext);
}
/ / mapSingleChildIntoContext function
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} = bookKeeping;
let mappedChild = func.call(context, child, bookKeeping.count++);
if (Array.isArray(mappedChild)) {
/ / whether mappedChild is an array, if it is, again call mapIntoWithKeyPrefixInternal function
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if(mappedChild ! =null) {// If it is not an array and the mappedChild part is null
CloneAndReplaceKey is used to pass in the key value of mappedChild
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,
);
}
// Finally add the processed mappedChild node to result, which is the value we printed out on the console
result.push(mappedChild);
}
}
Copy the code
- TraverseAllChildrenImpl method called (core method)
// The function takes four arguments
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
// Determine the type of children
var type = typeof children;
// If the type is undefined or Boolean, let children be null
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
// Define a Boolean type variable to determine whether to call the passed callback
var invokeCallback = false;
if (children === null) { // If the child node is null, set the identity variable to true-
invokeCallback = true;
} else { // If it is the opposite of the above, the specific type of type will be determined
switch (type) {
case 'string':
case 'number': // If it is a number, change the identifier to true
invokeCallback = true;
break;
case 'object': // This is an object. typeof
switch (children.?typeof) {
// is of type 'REACT_ELEMENT_TYPE' and does nothing
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE: // Yes REACT_PORTAL_TYPE changes flag to true
invokeCallback = true;
}
}
}
/ / if invokeCallback to true, calls incoming callback, namely mapSingleChildIntoContext function, it will return the new parameters
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.
// SEPARATOR is the initial '.' of the key. The SEPARATOR method is called getComponentKey when the passed nameSoFar is empty.
nameSoFar === ' ' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
return 1;
}
var child = void 0;
var nextName = void 0;
var subtreeCount = 0; // Count of children found in the current subtree.
// Define a nextNamePrefix. This is the key value of the second layer child node. The SUBSEPARATOR initial value is ':'.
var nextNamePrefix = nameSoFar === ' ' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// Check whether children is an array
if (Array.isArray(children)) { // is an array that loops through the child nodes
for (var i = 0; i < children.length; i++) {
child = children[i];
// The getComponentKey method is called to handle the key of a node
nextName = nextNamePrefix + getComponentKey(child, i);
// The traverseAllChildrenImpl is called, this time passing the children to the function, which is a json type of data, using recursion
subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
}
} else { // Not an array
var iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
{
// Warn about using Maps as children
if (iteratorFn === children.entries) { / / warning
! didWarnAboutMaps ? warning$1(false.'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.') : void 0;
didWarnAboutMaps = true;
}
}
var iterator = iteratorFn.call(children);
var step = void 0;
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 = ' ';
{
addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum();
}
var childrenString = ' ' + children;
(function () {
{
{
throw ReactError(Error('Objects are not valid as a React child (found: ' + (childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '} ' : childrenString) + '). + addendum));
}
}
}) ();
}
}
// The number of children is returned
return subtreeCount;
}
Copy the code
- The releaseTraverseContext method is called at the end
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
And then the result returned is what we printed in the console.
Map method (item => [item, [item, item])
import React from '.. /react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.map(props.children, item => [item, [item, item]]), 'map');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
At this point, the method passed in to expand the array function becomes different. Let’s go through the process again as described above to see if there are any differences or similarities between the two.
In the debugger, we found that the function execution process is the same as above, except that the number of times certain functions are executed has changed.
- TraverseAllChildrenImpl function
- MapSingleChildIntoContext function
The forEach method
We know that in ES6 syntax both the map and forEach methods iterate over an array, but the map method returns and the forEach method does not, so this article will not cover forEach.
toArray
import React from '.. /react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.toArray(props.children), 'toArray');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
Through the debugger, we find that the next step is the same as the map function.
count
import React from '.. /react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.count(props.children), 'count');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
The entry function for count
function countChildren(children) {
return traverseAllChildren(children, function () {
return null;
}, null);
}
Copy the code
It calls the flattening array function, which counted the number of nodes in the traverseAllChildrenImpl function when we wrote the source procedure above.
only
import React from '.. /react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.only(props.children[0]), 'only');
return props.children
}
export default() = > (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
Copy the code
Above, the result we print on the console is a JSON data containing the information we passed to the node. Now, let’s take a look at the source code
function onlyChild(children) {
// a closure function that will throw an exception if not a node is passed in
(function () {
if(! isValidElement(children)) {
{
throw ReactError(Error('React.Children.only expected to receive a single React element child.'));
}
}
}) ();
// If there is no problem, the node information is returned
return children;
}
Copy the code
After analyzing all five of the React. Children methods, we found that all methods except onlyChild execute the same method. So, in order to understand the process of the React. Children method more clearly and better, let’s draw a flow chart to feel the process.
Application of number group flattening in ES5 and ES6
- es5
function mapChildren(array) {
var result = [];
for(var i = 0; i <array.length; i++) {
if (Array.isArray(array[i])) {
// Recursive thinking
result = result.concat(mapChildren(array[i]))
} else {
result.push(array[i])
}
}
return result;
}
const result = mapChildren([1[1.2[3.4.5]]])
console.log(result); / /,1,2,3,4,5 [1]
Copy the code
- es6
function mapChildren(array) {
while(array.some(item => Array.isArray(item)))
array = [].concat(...array);
return array
}
const result = mapChildren([1[1.2[3.4.5]]])
console.log(result); / /,1,2,3,4,5 [1]
Copy the code
conclusion
The react. children source code is now complete, so we will learn the framework ideas, expand our thinking, put these ideas into practice, and improve coding habits to write high-quality code
If the above article is wrong, please show us, we learn together, common progress ~
Finally, share my public number [web front-end diary], attention after the data can be received (general people I do not tell oh) ~
Phase to recommend
- React API: Do you know the React API
- JQuery source code analysis (a) : read the source code structure, change the inherent way of thinking
- There are two ways to encapsulate ajax requests