React.Children 常用API源码解读
我们在写React组件时候,经常会有通过this.props.children传递给组件进行渲染包裹页面,通常会通过this.props.children.map()来遍历渲染,其实React有提供一个API来做这里。也就是我们今天要说的React.Children.map
问题
- 直接数组的map和React.Children.map有什么区别
- React.Children.map和React.Children.forEach有什么区别
首先,我们写个例子
import React from "react";
import "./App.css";
function ComWarp(props) {
return (
<div>
{React.Children.map(props.children, (c) => (
<div className="bg">{c}</div>
))}
</div>
);
}
function App() {
return (
<div className="App">
<ComWarp>
<div>1</div>
<div>2</div>
</ComWarp>
</div>
);
}
export default App;
上面写了组件ComWarp,接受props.children来进行处理
var Children = {
map: mapChildren,
forEach: forEachChildren,
count: countChildren,
toArray: toArray,
only: onlyChild
};
React的Children是个对象,里面有map、forEach等等方法,map对应的就是mapChildren函数,我们看看这个函数做了什么
function mapChildren(children, func, context) {
if (children == null) {
return children;
}
var result = [];
var count = 0;
mapIntoArray(children, result, '', '', function (child) {
return func.call(context, child, count++);
});
return result;
}
- children - 我们传递进来的ComWarp包裹的子内容是个数组,里面是每个div的对象($$type是react.element)
- 创建了一个空的数组result最后返回,我们继续看看mapIntoArray函数,这个函数比较长,我们分几个部分来说
function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
return subtreeCount;
}
第一部分
var type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
var invokeCallback = false;
if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
第一部分是判断传入的children的类型,上面说了children是一个数组,那也就是object类型,走下switch object,发现没有case符合,因为只是个纯数组而已没有$$typeof,他的子级才会有
var child;
var nextName;
var subtreeCount = 0; // Count of children found in the current subtree.
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getElementKey(child, i);
subtreeCount += mapIntoArray(child, array, escapedPrefix, nextName, callback);
}
} else {
var iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
var iterableChildren = children;
{
// Warn about using Maps as children
if (iteratorFn === iterableChildren.entries) {
if (!didWarnAboutMaps) {
warn('Using Maps as children is not supported. ' + 'Use an array of keyed ReactElements instead.');
}
didWarnAboutMaps = true;
}
}
var iterator = iteratorFn.call(iterableChildren);
var step;
var ii = 0;
while (!(step = iterator.next()).done) {
child = step.value;
nextName = nextNamePrefix + getElementKey(child, ii++);
subtreeCount += mapIntoArray(child, array, escapedPrefix, nextName, callback);
}
} else if (type === 'object') {
var childrenString = '' + children;
{
{
throw Error( "Objects are not valid as a React child (found: " + (childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString) + "). If you meant to render a collection of children, use an array instead." );
}
}
}
}
这里其实就会判断children是个数组的话(成立),就会遍历children,得到每个子级继续进行mapIntoArray(看到没递归函数)
nextName定义一个常量+本身的key,用于动态生成key给没个元素的props添加后续方法cloneAndReplaceKey
i=0,child的子级就是具体某一个react元素,继续执行mapIntoArray,此时我们再返回到第一部分,子级就是一个object,且是个react元素$$typeof是REACT_ELEMENT_TYPE, invokeCallback设置true,我们继续看true后会执行什么
if (invokeCallback) {
var _child = children;
var 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:
var childKey = nameSoFar === '' ? SEPARATOR + getElementKey(_child, 0) : nameSoFar;
if (Array.isArray(mappedChild)) {
var escapedChildKey = '';
if (childKey != null) {
escapedChildKey = escapeUserProvidedKey(childKey) + '/';
}
mapIntoArray(mappedChild, array, escapedChildKey, '', function (c) {
return 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
escapedPrefix + ( // $FlowFixMe Flow incorrectly thinks React.Portal doesn't have a key
mappedChild.key && (!_child || _child.key !== mappedChild.key) ? // $FlowFixMe Flow incorrectly thinks existing element's key can be a number
escapeUserProvidedKey('' + mappedChild.key) + '/' : '') + childKey);
}
array.push(mappedChild);
}
return 1;
}
执行了我们传递过来的callback函数也就是(c) => ()),执行完后判断返回的mappedChild的类型,如果不是数组且不是null,就会把mappedChild放入到array(一开始我们定义的空数组)
如果mappedChild是数组,也就是我们传递的(c) =>[c,c],那就会继续mapIntoArray,此时mapIntoArray的chilren是返回的[c,c]的数组,callback改造成c=>c,而不是开始的(c) =>[c,c]
最后往复递归形成新的一个数组result
流程图
image.pngReact.Children.forEach
function forEachChildren(children, forEachFunc, forEachContext) {
mapChildren(children, function () {
forEachFunc.apply(this, arguments); // Don't return anything.
}, forEachContext);
}
调用的也是mapChildren函数,区别于map是map会返回一个新的数组,而forEach只是处理forEachFunc不返回
toArray
function toArray(children) {
return mapChildren(children, function (child) {
return child;
}) || [];
}
调用mapChildren,返回处理好的数组
onlyChild 返回react元素
function onlyChild(children) {
if (!isValidElement(children)) {
{
throw Error( "React.Children.only expected to receive a single React element child." );
}
}
return children;
}
function isValidElement(object) {
return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
}
如果不是react的元素,就抛出异常,是直接返回react元素
网友评论