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.jsfunction mapChildren(children: ?ReactNodeList,func: MapFunc,context: mixed,
): ?Array<React$Node> {// 判断传入的子元素列表是否是 null 或 undefined,条件成立,直接返回 childrenif (children == null) {// $FlowFixMe limitation refining abstract types in Flowreturn 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 赋值 nullif (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,然后赋值给 mappedChildlet 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) 否则使用 nameSoFarconst childKey =nameSoFar === '' ? SEPARATOR + getElementKey(child, 0) : nameSoFar;// 判断处理后的子元素是否是数组【Array.isArray】 if (isArray(mappedChild)) {let escapedChildKey = '';// 如果 childKey 不为 null,则将 childKey 的单/替换为//,结果赋值给 escapedChildKeyif (childKey != null) {escapedChildKey = escapeUserProvidedKey(childKey) + '/';}// 如果是数组,递归调用 mapIntoArraymapIntoArray(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 childrenescapedPrefix +// $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-coercionconst 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 组件内部获取他的子元素,然后再再子元素的外层添加一个盒子,然后在盒子上实现我们需要的动画效果,比如渐入渐出、放大缩小,滑动等动画效果;
- 目前想到可以使用的就是瀑布流展示效果,动态获取每一列的高度,然后在动态的分配下一个子元素在那一列展示;
- 其他的应用应该还有很多,只是我在开发中遇到的比较少,以后有新的想法,再来补充!