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.

  • mapsimilararray.map
  • forEachsimilararray.forEach
  • countsimilararray.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, ifisValidElement(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

  • childrenIs a renderable nodemapSingleChildIntoContextPush children into the Result array
  • childrenIs an array, and is called again for each element in the arraytraverseAllChildrenImpl, the key passed in is the latest concatenated key
  • childrenIf yes, passchildren[Symbol.iterator]Gets the iterator of the objectiterator, put the result of iteration intotraverseAllChildrenImplTo 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

  • childrenChildren to deal with
  • nameSoFarThe parent key, which is passed layer by layer, is separated by:
  • callbackIf the current hierarchy is a renderable node,undefined,booleanWill becomenull.string,number,? typeofREACT_ELEMENT_TYPEorREACT_PORTAL_TYPETo callmapSingleChildIntoContextTo deal with
  • traverseContextAn 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

Welcome attention, daily progress!!