1. 使用方法
React.Children.map(children, function[(thisArg)])
2. 方法解释
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
3. 注意
如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。
4. React.Children.map 源码
4.1 mapChildren 源码
\packages\react\src\ReactChildren.js
function mapChildren(
children: ?ReactNodeList,
func: MapFunc,
context: mixed,
): ?Array<React$Node> {
// 判断传入的子元素列表是否是 null 或 undefined,条件成立,直接返回 children
if (children == null) {
// $FlowFixMe limitation refining abstract types in Flow
return children;
}
// 存储处理后的 child 列表
const result: Array<React$Node> = [];
// 每一个 child 的索引
let count = 0;
// 将 children 中的每个元素映射到数组 result 中,并在映射过程中应用 func 函数
mapIntoArray(children, result, '', '', function (child) {
// 调用映射函数处理 child,并返回处理结果
return func.call(context, child, count++);
});
return result;
}
4.2 mapChildren 函数解释
- children 是任意类型的子元素列表。
- func 操作子元素的映射函数,接受两个参数:子元素和索引,并返回一个映射后的结果。
- context 执行 func 映射函数的上下文。
- 检查 children 是否为 null 或 undefined,如果是,则直接返回 children。
- 创建一个名为 result 的空数组,用于存储处理后的子元素。
- 初始化一个计数器 count,用于记录当前处理的子元素的索引。
- 使用 mapIntoArray 函数遍历 children,并将每个子元素转换为数组形式存储在 result 中。mapIntoArray 函数还接受一个回调函数,该回调函数会调用 func 函数,并传入当前子元素和计数器的值。
- 返回处理后的 result 数组。
4.3 mapIntoArray 函数的实现
function mapIntoArray(
children: ?ReactNodeList,
array: Array<React$Node>,
escapedPrefix: string,
nameSoFar: string,
callback: (?React$Node) => ?ReactNodeList,
): number {
// 使用 typeof 获取 children 的类型
const type = typeof children;
// 判断 type 是否是 undefined 或者 boolean,跳转成立,将 children 赋值 null
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
// 是否调用回调函数标记
let invokeCallback = false;
// 如果 children 是 null,则调用回调
if (children === null) {
invokeCallback = true;
} else {
// 判断 type 是 bigint、string、number 直接调用回调
switch (type) {
case 'bigint':
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
// 如果 type 是 object,继续判断 $$typeof 的值
switch ((children: any).$$typeof) {
// 如果 $$typeof 是 REACT_ELEMENT_TYPE、REACT_PORTAL_TYPE直接进入回调
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
break;
// 如果 $$typeof 是 REACT_LAZY_TYPE 进行递归调用
case REACT_LAZY_TYPE:
const payload = (children: any)._payload;
const init = (children: any)._init;
return mapIntoArray(
init(payload),
array,
escapedPrefix,
nameSoFar,
callback,
);
}
}
}
// 如果 invokeCallback 是 true,直接调用回调
if (invokeCallback) {
const child = children;
// 使用回调函数处理 child,然后赋值给 mappedChild
let mappedChild = callback(child);
// 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:
// 获取子元素的key,如果 nameSoFar 是空,直接使用 SEPARATOR + getElementKey(child, 0) 否则使用 nameSoFar
const childKey =
nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;
// 判断处理后的子元素是否是数组【Array.isArray】
if (isArray(mappedChild)) {
let escapedChildKey = '';
// 如果 childKey 不为 null,则将 childKey 的单/替换为//,结果赋值给 escapedChildKey
if (childKey != null) {
escapedChildKey = escapeUserProvidedKey(childKey) + '/';
}
// 如果是数组,递归调用 mapIntoArray
mapIntoArray(mappedChild, array, escapedChildKey, '', c => c);
} else if (mappedChild != null) {
// 如果是 react 元素
if (isValidElement(mappedChild)) {
// 复制一份新的子元素
const newChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
escapedPrefix +
// $FlowFixMe[incompatible-type] Flow incorrectly thinks React.Portal doesn't have a key
(mappedChild.key != null &&
(!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(
// $FlowFixMe[unsafe-addition]
'' + mappedChild.key, // eslint-disable-line react-internal/safe-string-coercion
) + '/'
: '') +
childKey,
);
mappedChild = newChild;
}
// 将子元素放入存储的数组中
array.push(mappedChild);
}
return 1;
}
let child;
let nextName;
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// 如果 invokeCallback 为 false,
// children 是否为数组
if (isArray(children)) {
// 遍历 children 中每个元素,调用 mapIntoArray 函数,并将返回的子树计数累加到 subtreeCount 中
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getElementKey(child, i);
subtreeCount += mapIntoArray(
child,
array,
escapedPrefix,
nextName,
callback,
);
}
} else {
// children 是一个可迭代对象,则使用 iteratorFn 函数获取迭代器,并遍历每个元素,调用 mapIntoArray 函数,并将返回的子树计数累加到 subtreeCount 中
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
const iterableChildren: Iterable<React$Node> & {
entries: any,
} = (children: any);
const iterator = iteratorFn.call(iterableChildren);
let step;
let ii = 0;
// $FlowFixMe[incompatible-use] `iteratorFn` might return null according to typing.
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getElementKey(child, ii++);
subtreeCount += mapIntoArray(
child,
array,
escapedPrefix,
nextName,
callback,
);
}
} else if (type === 'object') {
// children 是一个对象,则检查其是否有 then 方法,如果有,则将其视为一个 Promise,并递归调用 mapIntoArray 函数
if (typeof (children: any).then === 'function') {
return mapIntoArray(
resolveThenable((children: any)),
array,
escapedPrefix,
nameSoFar,
callback,
);
}
// children 是一个对象,但没有 then 方法,则将其转换为字符串,并抛出一个错误,因为对象不能作为 React 子元素
// eslint-disable-next-line react-internal/safe-string-coercion
const childrenString = String((children: any));
throw new Error(
`Objects are not valid as a React child (found: ${
childrenString === '[object Object]'
? 'object with keys {' +
Object.keys((children: any)).join(', ') +
'}'
: childrenString
}). ` +
'If you meant to render a collection of children, use an array ' +
'instead.',
);
}
}
// 返回 subtreeCount,表示处理的子元素总数
return subtreeCount;
}
4.4 总结
目前是第一次看这个函数的实现,知道他大概干了什么,也大概明白每一步要干什么,但是目前还不清除他为什么要这么干。为什么要来看他的实现呢?一个是只有知道他是怎么实现的,才能知道在调用这个方法时,那些情况会报错,为什么报错,能够快速处理。使用这个方法开发功能的时候能够更加得心应手。
5. React.Children.map 应用
- swiper 组件的开发,或者说类 swiper 组件的开发,都需要在 swiper 组件内部获取他的子元素,然后再再子元素的外层添加一个盒子,然后在盒子上实现我们需要的动画效果,比如渐入渐出、放大缩小,滑动等动画效果;
- 目前想到可以使用的就是瀑布流展示效果,动态获取每一列的高度,然后在动态的分配下一个子元素在那一列展示;
- 其他的应用应该还有很多,只是我在开发中遇到的比较少,以后有新的想法,再来补充!