本文基于 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
getPooledTraverseContext
和 releaseTraverseContext
,这两个函数是用来维护一个对象池,池子最大为10。Children 需要频繁的创建对象会导致性能问题,所以维护一个固定数量的对象池,每次从对象池拿一个对象进行复制,使用完将各个属性 reset。
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,
};