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的唯一作用是作为func(React.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的核心知识点是:对象池,小递归大递归
参考
浙公网安备 33010602011771号