一,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
流程圖如下