Preface:

This article is based on the 16.13.1 React version. Compared with the previous version, to remove the concept of **pool, and the code in the function of the call has also been simplified and optimized, so that we understand the source more convenient. 六四屠杀

React.Children is one of the top-level apis that provides tools for handling this.props. Children, which can be any data type. React.Children has 5 methods: React.children.map (), react.children.foreach (), react.children.count (), react.children.only (), react.children.toarray (). They are all in the reactchildren.js file:

/packages/react/src/ReactChildren.js

export {
  forEachChildren as forEach,
  mapChildren as map,
  countChildren as count,
  onlyChild as only,
  toArray,
};
Copy the code

This article is mainly to analyze the map method, understand the map inside the processing logic, the other several can be connected by analogy.

First, let’s take the following code block as an example, take map source code to explain easy to understand.

In this case, props. Children is an array containing two children, and the printed result is as follows:

A, the React. Children. The map ()

1. The flow chart

2. Source code analysis

Let’s do the following and think about what the final output will be.

React.children. Map (props. Children, c => [c, [c, c]]);Copy the code

Enter the source code for analysis:

function mapChildren( children: ? Func: MapFunc, // c => [c, [c, c]] context: mixed,):? Array<React$Node> { if (children == null) { return children; } const result = []; let count = 0; mapIntoArray(children, result, '', '', function(child) { return func.call(context, child, count++); }); return result; }Copy the code

// 第一次children是初始值:{
  children: [{...}, {...}],
  array: [],
  '',
  '',
  c => [c, [c, c]],
};
// 第二次,遍历children里的第一项:{
  child: {...}, 
  array: [], 
  escapedPrefix: '', 
  nextName: '.0', 
  callback: c => [c, [c, c]],
}
// 第三次,children的第一个子节点的执行结果入参:{
  mappedChild: [c, [c, c]],
  array: [],
  escapedChildKey: '.0/',
  '',
  c => c
}
// 第四次,children的第一个子节点的执行结果的第一个item:{
  child: c, // 还是第一个子节点
  array: [], 
  escapedPrefix: '.0/', 
  nextName: '.0', 
  callback: c => c,
}
// 第五次,children的第一个子节点的执行结果的第二个item:{
  child: [c,c], 
  array: [c], 
  escapedPrefix: '.0/', 
  nextName: '.1', 
  callback: c => c,
}
// 第六次,children的第一个子节点的执行结果的第二个item[c,c]的第一项:{
  child: c,
  array: [c], 
  escapedPrefix: '.1/', 
  nextName: '.1:0', 
  callback: c => c,
}
// 第七次,children的第一个子节点的执行结果的第二个item[c,c]的第二项,参数{
  child: c,
  array: [c,c], 
  escapedPrefix: '.1/', 
  nextName: '.1:1', 
  callback: c => c,
}
// 第八次,children的第二个子节点:同理
// ...

function mapIntoArray(
  children: ?ReactNodeList,
  array: Array<React$Node>,
  escapedPrefix: string,
  nameSoFar: string,
  callback: (?React$Node) => ?ReactNodeList,
): number {
  const type = typeof children;

  if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
  }

  let invokeCallback = false;

  if (children === null) {
    invokeCallback = true;
  } else {
    switch (type) {
      case 'string':
      case 'number':
        invokeCallback = true;
        break;
      case 'object':
        switch ((children: any).$typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_PORTAL_TYPE:
            invokeCallback = true;
        }
    }
  }

  // children的第一个子节点符合条件,进入判断 
  // children的第一个子节点的执行结果的第一个item,进入判断 
  // children的第一个子节点的执行结果的第二个item[c,c]的第一项,进入判断
  // children的第一个子节点的执行结果的第二个item[c,c]的第二项,进入判断
  if (invokeCallback) {
    const child = children;

    // 执行callback,callback为 c => [c, [c, c]], mappedChild为[c, [c, c]];
    // 执行callback,callback为 c => c, mappedChild为c;
    // 执行callback,callback为 c => c, mappedChild为c;
    // 执行callback,callback为 c => c, mappedChild为c;
    let mappedChild = callback(child);

    // children的第一个子节点,childKey='.0';
    // children的第一个子节点的执行结果的第一个item,childKey='.0';
    // children的第一个子节点的执行结果的第二个item[c,c]的第一项,childKey='.1:0';
    // children的第一个子节点的执行结果的第二个item[c,c]的第二项,childKey='.1:1';
    const childKey =
      nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;

    // children的第一个子节点mappedChild满足数组条件
    // children的第一个子节点的执行结果的第一个item不满足数组条件
    // children的第一个子节点的执行结果的第二个item[c,c]的第一项不满足数组条件
    // children的第一个子节点的执行结果的第二个item[c,c]的第二项不满足数组条件
    if (Array.isArray(mappedChild)) {
      let escapedChildKey = '';
      if (childKey != null) {
        // escapedChildKey为'.0/'
        escapedChildKey = escapeUserProvidedKey(childKey) + '/';
      }
      // children的第一个子节点的执行结果入参:{
        mappedChild: [c, [c, c]],
        array: [],
        escapedChildKey: '.0/',
        '',
        c => c
      }
      mapIntoArray(mappedChild, array, escapedChildKey, '', c => c);
    } else if (mappedChild != null) {
      if (isValidElement(mappedChild)) {
        // clone节点的信息,替换了key值
        mappedChild = cloneAndReplaceKey(
          mappedChild,
          // Keep both the (mapped) and old keys if they differ, just as
          // traverseAllChildren used to do for objects as children
          escapedPrefix +
            // $FlowFixMe Flow incorrectly thinks React.Portal doesn't have a key
            (mappedChild.key && (!child || child.key !== mappedChild.key)
              ? // $FlowFixMe Flow incorrectly thinks existing element's key can be a number
                escapeUserProvidedKey('' + mappedChild.key) + '/'
              : '') +
            childKey,
        );
      }
      // children的第一个子节点的执行结果的第一个item, '.0/.0'
      // children的第一个子节点的执行结果的第二个item[c,c]的第一项,'.0/.1:0'
      // children的第一个子节点的执行结果的第二个item[c,c]的第二项, '.0/.1:1'
      // 同理可得:'.1/.0'
      // 同理可得:'.1/.1:0'
      // 同理可得:'.1/.1:1'
      array.push(mappedChild);
    }
    return 1;
  }

  let child;
  let nextName;
  let subtreeCount = 0; // Count of children found in the current subtree.
  const nextNamePrefix =
    nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

  // 第一次:直接走到这里,此时children是初始的那个数组;
  // 第二次:children的第一个子节点的执行结果走到这里;
  // 第三次:children的第一个子节点的执行结果的第二项走到这里;
  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) {
      child = children[i];
      nextName = nextNamePrefix + getElementKey(child, i);

      // 第一次children的第一个子节点:参数{
        child: {...}, // 第一个子节点
        array: [], 
        escapedPrefix: '', 
        nextName: '.0', 
        callback: c => [c, [c, c]],
      }
      // 第二次children的第一个子节点的执行结果的第一个item,遍历0,参数{
        child: c,
        array: [], 
        escapedPrefix: '.0/', 
        nextName: '.0', 
        callback: c => c,
      }
      // 第三次children的第一个子节点的执行结果的第二个item,遍历1,参数{
        child: [c,c],
        array: [c], 
        escapedPrefix: '.0/', 
        nextName: '.1', 
        callback: c => c,
      }
      // 第四次children的第一个子节点的执行结果的第二个item[c,c]的第一项,遍历0,参数{
        child: c,
        array: [c], 
        escapedPrefix: '.1/', 
        nextName: '.1:0', 
        callback: c => c,
      }
      // 第五次children的第一个子节点的执行结果的第二个item[c,c]的第二项,遍历1,参数{
        child: c,
        array: [c,c], 
        escapedPrefix: '.1/', 
        nextName: '.1:1', 
        callback: c => c,
      }
      // 第六次children的第二个子节点:同理{
        child: {...}, // 第二个子节点
        array: [c,c,c], 
        escapedPrefix: '', 
        nextName: '.1', 
        callback: c => [c, [c, c]],
      }
      subtreeCount += mapIntoArray(
        child,
        array,
        escapedPrefix,
        nextName,
        callback,
      );
    }
  } else {
    const iteratorFn = getIteratorFn(children);
    if (typeof iteratorFn === 'function') {
      const iterableChildren: Iterable<React$Node> & {
        entries: any,
      } = (children: any);

      if (__DEV__) {
        // Warn about using Maps as children
        if (iteratorFn === iterableChildren.entries) {
          if (!didWarnAboutMaps) {
            console.warn(
              'Using Maps as children is not supported. ' +
                'Use an array of keyed ReactElements instead.',
            );
          }
          didWarnAboutMaps = true;
        }
      }

      const iterator = iteratorFn.call(iterableChildren);
      let step;
      let ii = 0;
      while (!(step = iterator.next()).done) {
        child = step.value;
        nextName = nextNamePrefix + getElementKey(child, ii++);
        subtreeCount += mapIntoArray(
          child,
          array,
          escapedPrefix,
          nextName,
          callback,
        );
      }
    } else if (type === 'object') {
      const childrenString = '' + (children: any);
      invariant(
        false,
        'Objects are not valid as a React child (found: %s). ' +
          'If you meant to render a collection of children, use an array ' +
          'instead.',
        childrenString === '[object Object]'
          ? 'object with keys {' + Object.keys((children: any)).join(', ') + '}'
          : childrenString,
      );
    }
  }

  return subtreeCount;
}
Copy the code

Execution Result:

3. Conclusion:

A map is a tiling of a nested array and return the result. As you can see, when the same node returns at the end (e.g. 0, 1, 2, they are the first child node), their other attributes are the same except the key value. Key generation rules I have made a detailed comment in the code, you can go to the above code to see (I write node does not set the key case).

So let’s think about it, what’s the value of a key nested in another layer?

React.Children.map(props.children, c => [c, [c, [c,c]]])
Copy the code

His results were as follows:

Do you see the pattern? When the node itself is not set with a key, the subscript before/represents the node’s position, the subscript after/represents the current element’s position in the array, and the depth is denoted by:.

What if we set the key ourselves? His presentation is as follows, and you can summarize for yourself the difference between a key and no key.

Second, the React. Children. ForEach ()

As with react.children.map (), the difference is that there is no return

function forEachChildren( children: ? ReactNodeList, forEachFunc: ForEachFunc, forEachContext: mixed, ): void { mapChildren( children, function() { forEachFunc.apply(this, arguments); }, forEachContext, ); }Copy the code

Three, the React. Children. The count ()

Count the number of children

function countChildren(children: ? ReactNodeList): number { let n = 0; mapChildren(children, () => { n++; }); return n; }Copy the code

Four, the React. Children. Only ()

Check if children has only one child, if so, return the child, otherwise throw an error.

function onlyChild<T>(children: T): T {
  invariant(
    isValidElement(children),
    'React.Children.only expected to receive a single React element child.',
  );
  return children;
}
Copy the code

Fifth, the React. Children. ToArray ()

Convert children to an array, and then you can manipulate children using the methods in the array

function toArray(children: ? ReactNodeList): Array<React$Node> { return mapChildren(children, child => child) || []; }Copy the code

Conclusion:

These 5 functions except only, all call the method mapChildren traversal children. Understand the map operation, and the rest will follow.