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,

      count0

    };

  }

}

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