React.Children

React.Children

仅仅用于处理this.props.children不透明数据结构

不透明数据结构:

  • 当组件没有子节点时,this.props.children等于undefined
  • 当组件只有一个子节点时,this.props.children等于Object
  • 当组件有多个子节点时,this.props.children等于Array

Function

  • React.Children.map

  • React.Children.foreach

  • React.Children.count

  • React.Children.only

  • React.Children.toArray

React.Children.map

React.Children.map(children, function[(thisArg)])

React.js

import {forEach, map, count, toArray, only} from './ReactChildren';

React.Children.js

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

mapChildren

function mapChildren(children, func, context) {
  if (children == null) {
    return children;
  }
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}
  • context

context的唯一作用是作为funcReact.Children.map时传入方法)的环境变量,但是平时一般不会传值,默认值一般就为null

// from function mapSingleChildIntoContext

let mappedChild = func.call(context, child, bookKeeping.count++);

mapIntoWithKeyPrefixInternal

function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { 
  let escapedPrefix = '';
  if (prefix != null) {
    escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  }
  const traverseContext = getPooledTraverseContext(
    array,
    escapedPrefix,
    func,
    context,
  );

  traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
  releaseTraverseContext(traverseContext);
}

mapChildren调用该方法时传入的参数

children => 调用this.props.children传入的children
array => mapChildren新建的空数组
prefix => null, prefix != null 始终等于false
func => 调用this.props.children传入的方法
context => 上面介绍过了,环境变量

通过容量池方法getPooledTraverseContext获取traverseContext对象

// traverseContext对象的格式以及每个value对应的值来源

{
  result: mapResult, => array => mapChildren新建的空数组
  keyPrefix: keyPrefix, => prefix => null, prefix != null 始终等于false
  func: mapFunction, => func => 调用this.props.children传入的方法
  context: mapContext, => context => 上面介绍过了,环境变量
  count: 0,
}

count
用作函数的下标,在mapSingleChildIntoContext里面用到的

let mappedChild = func.call(context, child, bookKeeping.count++); 

traverseAllChildren

function traverseAllChildren(children, callback, traverseContext) {
  if (children == null) {
    return 0;
  }

  return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

上面的参数分别对应

children => 调用this.props.children传入的children
callback => 对应mapSingleChildIntoContext方法
traverseContext => 通过`getPooledTraverseContext`获取`traverseContext`对象

traverseAllChildrenImpl

该方法是整个React.Children.map的逻辑核心

function traverseAllChildrenImpl( // // children-子节点, mapSingleChildIntoContext-当前定义的function, traverseContext-对象  nameSoFar没意义
  children,
  nameSoFar,
  callback,
  traverseContext,
) {
  const type = typeof children;

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

  let invokeCallback = false; // invokeCallback  调用回调 mapSingleChildIntoContext

  if (children === null) {
    invokeCallback = true;
  } else {
    // 判断组件中this.props.children的值不是多个子节点,即不是数组类型
    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 (invokeCallback) { // 当有多个子节点时执行 mapSingleChildIntoContext
    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.
      nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
    );
    return 1;
  }
  
 // ==========  分割线 ======= 上面是第一段,下面是第二段

  let child;
  let nextName;
  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++) {
      child = children[i];
      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,
        );
      }
    } 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;
}

整个方法可以分成第一段和第二段,分开研究。

第一段

children不是Array类型时,执行callback方法 (即mapSingleChildIntoContext)。也就是说当this.props.children的值不是数组时,会立刻标记flag invokeCallback为true,触发callback方法。

第二段

当通过Array.isArray(children)判断children是数组时,循环遍历children,分别重新调用traverseAllChildrenImpl方法。重新经过第一段代码判断每个child是否是满足直接调用callback的条件。

这是一种递归思想

  • 注意通过for循环遍历children后重新调用自身traverseAllChildrenImpl时,传入的参数不是children而是child。最终callback里面的参数也不是children,是child
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.
  nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);

mapSingleChildIntoContext

function mapSingleChildIntoContext(bookKeeping, child, childKey) {
  const {result, keyPrefix, func, context} = bookKeeping; 

  let mappedChild = func.call(context, child, bookKeeping.count++);
  if (Array.isArray(mappedChild)) {
    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
  } else if (mappedChild != null) {
    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,
      );
    }
    result.push(mappedChild);
  }
}

这里面也用到递归思想,如果func的返回值是数组,调用mapIntoWithKeyPrefixInternal进行大范围的循环调用,注意调用时的传参

mappedChild => 调用func时返回的值
result => 在最开始就创建的,最终都会push到公共数组result中
c => c  => 返回自己,将数组mappedChild展开存到result中

结语

最终React.Children.map返回的是一个一元数组

一元数组 [1, 2, 3], 二元数组 [1, 2, [3, 4]]

React.Children.map的核心知识点是:对象池,小递归大递归

参考

section3-2
posted @ 2019-11-20 16:54  zhh海军上将  阅读(339)  评论(0)    收藏  举报