React 源码系列 | React Children 详解 | Children 中 key 内部生成原理

本文详细解析了React Children API,包括map、forEach、count、toArray和only等方法,特别探讨了key的内部生成机制。通过实例和源码分析,阐述了React如何处理和遍历子元素,以及map函数如何处理多维数组并生成新的key。文章还提到了对象池的概念,以提高性能。最后,介绍了forEach的简化实现和count用于计算子元素数量的功能。
摘要由CSDN通过智能技术生成

本文基于 React V16.8.6,本文代码地址

  • 测试代码 https://github.com/lxfriday/react-source-analyse/commit/ad4135e21ff6841597d74b93e6037227ff3e4d04

  • 源码讲解 https://github.com/lxfriday/react-interpretation/commit/149e76748f61df9b80ef96aea279c49ffe59e287

React 中一个元素可能有 0 个、1 个或者多个直接子元素,React 导出的 Children 中包含 5 个处理子元素的方法。

  • map 类似 array.map

  • forEach 类似 array.forEach

  • count 类似 array.length

  • toArray

  • only

React 内部处理 Children 的几个重要函数包括

  • mapChildren

  • traverseAllChildrenImpl

  • mapIntoWithKeyPrefixInternal

  • mapSingleChildIntoContext

  • getPooledTraverseContext

  • releaseTraverseContext

源码都在 packages/react/src/ReactChildren.js 中。

导出的语句

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

Children API

map

类似 array.map,但有一下几个不同点:

  • 返回的结果一定是一个一维数组,多维数组会被自动摊平

  • 对返回的每个节点,如果 isValidElement(el) === true ,则会给它加上一个 key,如果元素本来就有 key,则会重新生成一个新的 key

map 的用法:第一个参数是要遍历的 children,第二个参数是遍历的函数,第三个是 context,执行遍历函数时的 this

如果 children == null,则直接返回了。

mapChildren
/**
 * Maps children that are typically specified as `props.children`.
 * 用来遍历 `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;
  }
  // 遍历出来的元素会丢到 result 中最后返回出去
  const result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}
mapIntoWithKeyPrefixInternal

将 children 完全遍历,遍历的节点最终全部存到 array 中,是 ReactElement 的节点会更改 key 之后再放到 array 中。

function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
  // 这里是处理 key,不关心也没事
  let escapedPrefix = '';
  if (prefix != null) {
    escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  }
  // getPooledTraverseContext 和 releaseTraverseContext 是配套的函数
  // 用处其实很简单,就是维护一个大小为 10 的对象重用池
  // 每次从这个池子里取一个对象去赋值,用完了就将对象上的属性置空然后丢回池子
  // 维护这个池子的用意就是提高性能,毕竟频繁创建销毁一个有很多属性的对象消耗性能
  const traverseContext = getPooledTraverseContext(
    array, // result 
    escapedPrefix, // ''
    func, // mapFunc
    context, // context
  );
  // 最核心的一句
  traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
  releaseTraverseContext(traverseContext);
}

getPooledTraverseContext

getPooledTraverseContextreleaseTraverseContext这两个函数是用来维护一个对象池,池子最大为10。Children 需要频繁的创建对象会导致性能问题,所以维护一个固定数量的对象池,每次从对象池拿一个对象进行复制,使用完将各个属性 reset。

640?wx_fmt=png
const POOL_SIZE = 10;
const traverseContextPool = [];
// 返回一个传入参数构成的对象
// traverseContextPool 长度为 0 则自己构造一个对象出来,否则从 traverseContextPool pop 一个对象
// 再对这个对象的各个属性进行赋值
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,
    };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值