一,React.Children是什么?
是为了处理this.props.children(this.props.children表示所有组件的子节点)这个属性提供的工具,是顶层的api之一
React.children的用处:https://www.cnblogs.com/sunxiaopei/p/13805645.html
二,React.Children.map的结构及举例
结构:React.Children.map(object children,function fn [, object context])
举例如下:
class ChildrenDemo extends React.Component {
render() {
return (
<ol>
{
React.Children.map(this.props.children, (child)=> {//callback child子节点
return <li>{child}</li>
})
}
</ol>
)
}
}
class MessageList extends React.Component {
render() {
return (
<ChildrenDemo>
<span>1</span>
<span>2</span>
</ChildrenDemo>
)
}
}
this.props.children值有三种情况
1.如果组件没有节点 值为undefined
2.如果组件有一个节点 值的数据类型为对象
3.如果组件有多个节点 值的数据类型为数组
React.Children.map(this.props.children, child => [child, [child]]):来遍历this.props.children的子节点 多层嵌套的 [child, [child]])通过map之后平铺成一维数组[child,child]
这里两个子节点<span>1</span>和<span>2</span>,这里每一个节点都是数组 通过React.Children.map变成一个一维数组
//children:被遍历的子组件 //func:单个子组件需要执行的函数 //context:func执行时候,this指针指的对象 function mapChildren(children, func, context) { if (children == null) { return children; } var result = []; //this.props.children [] null child=>[child,[child]] undefined mapIntoWithKeyPrefixInternal(children, result, null, func, context); //执行完mapIntoWithKeyPrefixInternal方法后 返回result return result; }
/* 第一次:children:[child1,child2] array:[] prefix:null func:child=>[child,[child]] context:undefined 第二次:children:[child1,[child1]] array:[] prefix:".0" func:c=>c context:undefined 第三次:children:[child2,[child2]] array:[] prefix:".1" func:c=>c context:undefined */ function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { var escapedPrefix = ''; //处理key 如果字符串有多个/ 在匹配的字符串后面加一个/ 一般第二层递归用到 第一层prefix为null if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + '/'; } //从traverseContextPool里面里面获取一个context var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context); //多个节点 循环每一个节点 将嵌套的数组展平 traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); //context对象清空然后放回到traverseContextPool里面 releaseTraverseContext(traverseContext); }
解析:escapeUserProvidedKey()/getPooledTraverseContext()/traverseAllChildren()/releaseTraverseContext()函数的包裹器
escapeUserProvidedKey()
function escapeUserProvidedKey(text) { //如果字符串中有多个/的话 在匹配的字符后加/ //let a='aa/a/' =>// aa/a// return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/'); }
//创建一个对象池 复用对象 从而减少对象带来的内存暂用 和性能消耗 var POOL_SIZE = 10; //对象池的最大容量 var traverseContextPool = [];//全局变量 对象池 有多少个数组,就有多少个对象,这就是traverseContextPool设置在这里的含义 function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) { //如果对象池内存在对象 则出队一个对象 if (traverseContextPool.length) {//如果全局变量有子节点 var traverseContext = traverseContextPool.pop();//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 }; } }
解析:创建一个对象池 复用对象 从而减少对象带来的内存暂用 和性能消耗
//traverseAllChildrenImpl的触发器 function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; } return traverseAllChildrenImpl(children, '', callback, traverseContext); }
解析:traverseAllChildrenImpl的触发器
releaseTraverseContext()
//将对象属性清空并且重新放入对象池中 function releaseTraverseContext(traverseContext) { traverseContext.result = null; traverseContext.keyPrefix = null; traverseContext.func = null; traverseContext.context = null; traverseContext.count = 0; if (traverseContextPool.length < POOL_SIZE) { traverseContextPool.push(traverseContext); } }
解析:将对象属性清空并且重新放入对象池中
traverseAllChildrenImpl()
//核心递归函数 目的为展平数组 //对于可以循环的children 都会重复调用traverseAllChildrenImpl 直到一个节点 然后调用callback function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) { var type = typeof children; if (type === 'undefined' || type === 'boolean') { //undefined null都被认为为null children = null; } //调用fun的flag var invokeCallback = false; if (children === null) { invokeCallback = true; } else { switch (type) { case 'string': case 'number': invokeCallback = true; break; //如果props.children单个节点 //递归traverAllChildenTml时 <span>1</span>和<span>2</span>作为child //必会invokeCallback=true case 'object': switch (children.$$typeof) { case REACT_ELEMENT_TYPE: case REACT_PORTAL_TYPE: invokeCallback = true; } } } if (invokeCallback) { 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. //如果只有一个节点 直接调用callback //<span>1</span> key=".0" nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar); return 1; } 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]; //不手动设置key的话 第一层第一个时。0 第二个。1 nextName = nextNamePrefix + getComponentKey(child, i); //把child作为参数 进行递归 直到为单个节点的时候 去调用callback subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext); } } else { var iteratorFn = getIteratorFn(children); if (typeof iteratorFn === 'function') { { // Warn about using Maps as children if (iteratorFn === children.entries) { !didWarnAboutMaps ? warning$1(false, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.') : void 0; didWarnAboutMaps = true; } } var iterator = iteratorFn.call(children); var step; var 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') { //如果是纯对象 var addendum = ''; { addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum(); } 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) + ")." + addendum); } } } } return subtreeCount; }
解析:当type的值为不可遍历的undefined/boolean/string/number/object时候 则直接调用callback 回调函数
核心递归函数 目的为展平数组,对于可以循环的children 都会重复调用0 直到一个节点 然后调用callback也就是mapSingleChildIntoContext
mapSingleChildIntoContext()
//复制除了key以外的属性 替换key属性 将其放到result中 bookKeeping:context对象 function mapSingleChildIntoContext(bookKeeping, child, childKey) { var result = bookKeeping.result, keyPrefix = bookKeeping.keyPrefix, func = bookKeeping.func, context = bookKeeping.context; //调用fun回调函数 var mappedChild = func.call(context, child, bookKeeping.count++); //对每一个节点 如果是数组 进行递归 if (Array.isArray(mappedChild)) { //如果返回一个数组 这里返回的是[child,[child]]是一维数组 //大递归 这次 不再调用fun 如果调用fun则无限循环 所以直接返回c mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, function (c) { return c; }); } else if (mappedChild != null) { if (isValidElement(mappedChild)) {//isValidElement 判断是否是合理的reactElement元素 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); } //替换一下key推入到result中 result.push(mappedChild); } }
解析: mapSingleChildIntoContext
这个方法其实就是调用React.Children.map(children, callback)
这里的callback
,就是我们传入的第二个参数,并得到map
之后的结果。注意重点来了,如果map
之后的节点还是一个数组,那么再次进入mapIntoWithKeyPrefixInternal
,那么这个时候我们就会再次从pool
里面去context
了,而pool
的意义大概也就是在这里了,如果循环嵌套多了,可以减少很多对象创建和gc
的损耗,而如果不是数组并且是一个合规的ReactElement
,就触达顶点了,替换一下key
就推入result
了
cloneAndReplaceKey()
//返回一个新的reactElement 替换了newKey 其他的都是老得reactElement function cloneAndReplaceKey(oldElement, newKey) { var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props); return newElement; }
解析:返回一个新的reactElement 替换了newKey 其他的都是老得reactElement
流程图如下