React 源码阅读-12

ReactChildren

ReactChildren.png

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

// invariant ,warning 用户报错警告
import invariant from 'shared/invariant';
import warning from 'shared/warning';
// 引入 react Symbol 对象类型标识
import {
  getIteratorFn,
  REACT_ELEMENT_TYPE,
  REACT_PORTAL_TYPE,
} from 'shared/ReactSymbols';
// isValidElement 检测是否是一个 reactelement对象
//cloneAndReplaceKey复制一个 React 元素并替换的留属性: key 值。
import {isValidElement, cloneAndReplaceKey} from './ReactElement';

//react 当前调试信息
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';

const SEPARATOR = '.';
const SUBSEPARATOR = ':';

/**
 * Escape and wrap key so it is safe to use as a reactid
 *
 * @param {string} key to be escaped.
 * @return {string} the escaped key.
 */

// 定义和转换key
// 将传入的key中所有的'='替换成'=0',':'替换成 '=2',并在key之前加上'$'
function escape(key) {
  const escapeRegex = /[=:]/g;
  const escaperLookup = {
    '=': '=0',
    ':': '=2',
  };
  const escapedString = ('' + key).replace(escapeRegex, function(match) {
    return escaperLookup[match];
  });

  return '$' + escapedString;
}

/**
 * TODO: Test that a single child and an array with one item have the same key
 * pattern.
 */

let didWarnAboutMaps = false;

const userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text) {
  // /如果字符串中有连续多个 / 的话,在匹配的字串后再加 /
  //   react对key定义的一个规则:
  // 如果字符串中有连续多个/的话,在匹配的字串后再加/
  // 这个函数一般是第二层递归时,会用到
  //匹配一个或者多个 "/",并用'$&/'替换
  return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
}

const POOL_SIZE = 10;
const traverseContextPool = [];
// 实际上维护了一个 size 为 10 的缓冲池.
// 如果 pool 中有存货, 则 pop 出一个进行使用.
// 如果 pool 中空空如也, 则 return 一个新的对象.
function getPooledTraverseContext(
  mapResult,
  keyPrefix,
  mapFunction,
  mapContext,
) {
  // func: 这就是用户传入的 forEach 处理函数
  // context: 这是个可选参数, 用户可以传入作为调用上述 func 时的上下文.
  // 看到这里你就知道, 默认情况下,
  // 你的 处理函数执行的时候, 是没有 context, 也就是处理函数中,this === undefined.
  // 如果想在 处理函数中绑定 this, 只能通过这个参数指定.
  // 这一点在后面分析 forEachSingleChild 会看到原理
  if (traverseContextPool.length) {
    // pop() 方法用于删除并返回数组的最后一个元素。
    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,
    };
  }
}

function releaseTraverseContext(traverseContext) {
  // 这个方法简单的不能再简单,
  // 核心目的就是 if 里面的那块代码,
  // 如果池数量小于 POOL_SIZE(上文中得知这个数字是 10),
  // 则把对象放回到池中, 以备后续使用.
  traverseContext.result = null;
  traverseContext.keyPrefix = null;
  traverseContext.func = null;
  traverseContext.context = null;
  traverseContext.count = 0;
  if (traverseContextPool.length < POOL_SIZE) {
    traverseContextPool.push(traverseContext);
  }
}

/**
 * @param {?*} children Children tree container.
 * @param {!string} nameSoFar Name of the key path so far.
 * @param {!function} callback Callback to invoke with each child found.
 * @param {?*} traverseContext Used to pass information throughout the traversal
 * process.
 * @return {!number} The number of children in this subtree.
 */
function traverseAllChildrenImpl(
  children,
  nameSoFar,
  callback,
  traverseContext,
) {
  const type = typeof children;
  // 这个分支处理了大部分类型. 这里的 children 实际上是单个对象, 并不是像它的名字一样是个复数.
  // 接下来执行 callback(traverseContext, children, nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar)
  // 并返回 1.
  // 看到这里, 我们就知道, children 不仅仅可以是 Component,
  // 还可以是 String/Boolean/Undefined 等等.
  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.$$typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_PORTAL_TYPE:
            invokeCallback = true;
        }
    }
  }

  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.
      // 这样一来,如果元素数量增加,这是一致的s
      nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,

      // 这个 callback 实际上是 forEachSingleChild
      // 而 forEachSingleChild 则调用 func.call(context, child, ...)
      // 实现了绑定上下文调用处理函数. 所以 callback(traverseContext, children, ...)
      // 可以简单理解为以 children(实际上是单个对象, 并不是集合, 名字容易误导读者) 为参数, 调用了用户的处理函数.
    );
    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)) {
    // !! 如果是 Array, 则深度递归 !!
    for (let i = 0; i < children.length; i++) {
      child = children[i];
      nextName = nextNamePrefix + getComponentKey(child, i);
      subtreeCount += traverseAllChildrenImpl(
        child,
        nextName,
        callback,
        traverseContext,
      );
    }
  } else {
    // !! 如果不是 Array, 则看该对象是否可迭代 !!
    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,
      );
    }
  }
  // React.Children.forEach 是不支持 Map 但却支持 Set 的.看代码中的注释,
  return subtreeCount;
}

/**
 * Traverses children that are typically specified as `props.children`, but
 * might also be specified through attributes:
 *
 * - `traverseAllChildren(this.props.children, ...)`
 * - `traverseAllChildren(this.props.leftPanelChildren, ...)`
 *
 * The `traverseContext` is an optional argument that is passed through the
 * entire traversal. It can be used to store accumulations or anything else that
 * the callback might find relevant.
 *
 * @param {?*} children Children tree object.
 * @param {!function} callback To invoke upon traversing each child.
 * @param {?*} traverseContext Context for traversal.
 * @return {!number} The number of children in this subtree.
 */

function traverseAllChildren(children, callback, traverseContext) {
  if (children == null) {
    return 0;
  }
  // traverseAllChildren 只是个入口, 真实的递归遍历是定义在 traverseAllChildrenImpl 中:
  // 递归遍历
  return traverseAllChildrenImpl(children, '', callback, traverseContext);
}

/**
 * Generate a key string that identifies a component within a set.
 *
 * @param {*} component A component that could contain a manual key.
 * @param {number} index Index that is used if a manual key is not provided.
 * @return {string}
 */
function getComponentKey(component, index) {
  // Do some typechecking here since we call this blindly. We want to ensure
  // that we don't block potential future ES APIs.
  // 所以在这里做一些类型检查。我们要确保
  // /不会阻止潜在的未来ES API。
  if (
    typeof component === 'object' &&
    component !== null &&
    component.key != null
  ) {
    // Explicit key
    //组件 key
    return escape(component.key);
  }
  // Implicit key determined by the index in the set
  return index.toString(36);
  // 转换成36进制
}

function forEachSingleChild(bookKeeping, child, name) {
  const {func, context} = bookKeeping;
  func.call(context, child, bookKeeping.count++);
}

/**
 * Iterates through children that are typically specified as `props.children`.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrenforeach
 *
 * The provided forEachFunc(child, index) will be called for each
 * leaf child.
 *
 * @param {?*} children Children tree container.
 * @param {function(*, int)} forEachFunc
 * @param {*} forEachContext Context for forEachContext.
 */
function forEachChildren(children, forEachFunc, forEachContext) {
  if (children == null) {
    return children;
  }
  const traverseContext = getPooledTraverseContext(
    null,
    null,
    forEachFunc,
    forEachContext,
  );
  // 其中, children 是要遍历的子节点对象,
  // traverseContext 是上一步封装了 处理函数 和 处理函数执行上下文 的一个对象.
  // 而 forEachSingleChild 则是真正调用处理函数的方法:
  traverseAllChildren(children, forEachSingleChild, traverseContext);
  releaseTraverseContext(traverseContext);
}

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);
  }
}

// getPooledTraverseContext()/traverseAllChildren()/releaseTraverseContext()

function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
  // 对prefix进行处理
  //如果字符串中有连续多个 / 的话,在匹配的字串后再加 /
  let escapedPrefix = '';

  if (prefix != null) {
    escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  }
  // 遍历上下文
  //traverseContext=
  // {
  //  result:[],
  //  keyPrefix:'',
  //  func:(item)=>{return [item,[item,] ]},
  //  context:undefined,
  //  count:0,
  // }
  const traverseContext = getPooledTraverseContext(
    array,
    escapedPrefix,
    func,
    context,
  );
  // 遍历所有的 Children 将嵌套的数组展平 绑定上下文调用处理函数
  // 其中, children 是要遍历的子节点对象,
  // traverseContext 是上一步封装了 处理函数
  // 和 处理函数执行上下文 的一个对象.
  // 而 mapSingleChildIntoContext 则是真正调用处理函数的方法:
  traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);

  // 这个方法简单的不能再简单,
  // 核心目的就是 if 里面的那块代码,
  // 如果池数量小于 POOL_SIZE(上文中得知这个数字是 10),
  // 则把对象放回到池中, 以备后续使用.
  releaseTraverseContext(traverseContext);
}

/**
 * Maps children that are typically specified as `props.children`.
 *  映射通常指定为`props.children`的子级
 * See https://reactjs.org/docs/react-api.html#reactchildrenmap
 *
 * The provided mapFunction(child, key, index) will be called for each
 * leaf child.
 *
 * @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;
  }
  const result = [];
  // 将 children 完全遍历,遍历的节点最终全部存到 array 中,是 ReactElement 的节点会更改 key 之后再放到 array 中。
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}

/**
 * Count the number of children that are typically specified as
 * `props.children`.
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrencount
 *
 * @param {?*} children Children tree container.
 * @return {number} The number of children.
 */
function countChildren(children) {
  return traverseAllChildren(children, () => null, null);
}

/**
 * Flatten a children object (typically specified as `props.children`) and
 * return an array with appropriately re-keyed children.
 *  展开一个成一个数组
 * See https://reactjs.org/docs/react-api.html#reactchildrentoarray
 */

function toArray(children) {
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, child => child);
  return result;
}
/**
 * Returns the first child in a collection of children and verifies that there
 * is only one child in the collection.
 * 返回子集合中的first child 并验证 只有一个
 *
 * See https://reactjs.org/docs/react-api.html#reactchildrenonly
 *
 * 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;
}

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

xiaoping
337 声望12 粉丝

保持学习,记一下自己的学习经历