Children: {
map,
forEach,
count,
toArray,
only,
}
這個 children 是個對象,這個對象裏面有 5 個屬性,這個 5 個屬性看上去就跟咱們數組的操做很是的像,前兩個是最重要的,也就是 map 和 forEach ,就至關於一個數組的 map 和 forEach 方法,咱們能夠理解爲意義是同樣的,可是實際的操做跟數組的 map 和 forEach 會有一點的區別。 這裏 map 是全部邏輯裏面最複雜的一個。而 map 和 forEach 是差很少的,他們惟一的區別是一個有返回,一個沒有返回,map 是經過咱們傳入一個方法以後,調用出來以後,返回的一個新的數組,而 forEach 是返回原數組的。react
import React from 'react' function ChildrenDemo(props) { console.log(props.children) console.log(React.Children.map(props.children, c => [c, [c, c]])) return props.children } export default () => ( <ChildrenDemo> <span>1</span> <span>2</span> </ChildrenDemo> )
咱們建立了一個組件,叫 ChildrenDemo ,而後裏面包裹了兩個 span 做爲他的 children ,而後在他的 props.children 裏面就能夠拿到,第一個打印出來的就是 props.children,咱們看到就是兩個 element 節點。 就跟以前的 React Element 屬性是同樣的。第二個是經過 React.Children.map 這個 api ,而後傳入 props.children 。 而且傳入了一個 callback ,這個 callback 返回的是一個嵌套兩層的數組,可能比較迷茫,能夠先當作 c => [c, c],而後打印出來的children分別是1,1,2,2,咱們是否能夠理解爲他最終返回的是一個展開的,由於這總共是兩個節點,props.children 是兩個節點,每一個節點經過 map function 以後返回的是一個數組,而後 React.Children 把他給展開了,而後就變成了一個一維數組,原本是返回一個二維數組,而後這個數組裏面有兩個一維數組。api
export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};
mapChildren as map,這裏 export 出去的 map ,在裏面就是 mapChildren 這個方法數組
function mapChildren(children, func, context) { if (children == null) { return children; } const result = []; mapIntoWithKeyPrefixInternal(children, result, null, func, context); return result; }
找到 mapChildren 這個方法,這裏面先判斷這個 children 是否等於 null ,若是等於 null ,就直接返回了。而後聲明瞭一個 result ,最終 return 的也是這個 result 。這個時候咱們再去看看 forEachChildren瀏覽器
function forEachChildren(children, forEachFunc, forEachContext) { if (children == null) { return children; } const traverseContext = getPooledTraverseContext( null, null, forEachFunc, forEachContext, ); traverseAllChildren(children, forEachSingleChild, traverseContext); releaseTraverseContext(traverseContext); }
這裏面的 forEachChildren 沒有result ,沒有返回值。這就是他們一個本質的區別。app
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { let escapedPrefix = ''; if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + '/'; } const traverseContext = getPooledTraverseContext( array, escapedPrefix, func, context, ); traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); releaseTraverseContext(traverseContext); }
mapIntoWithKeyPrefixInternal 裏面咱們看到他先處理了一下 key,這個 key 先忽略,由於他就是一個字符串處理相關的東西,沒有什麼特別的,下面再調用這個三個函數,跟 forEachChildren 對比下,是差很少的。經過調用 getPooledTraverseContext ,而後去獲取了一個 traverseContext ,這個東西有什麼意義呢?咱們直接看下這個方法 ide
const traverseContextPool = []; function getPooledTraverseContext( mapResult, keyPrefix, mapFunction, mapContext, ) { if (traverseContextPool.length) { const traverseContext = traverseContextPool.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, }; } }
咱們看到這個方法其實沒有什麼特別的處理,他首先判斷一下這個全局變量 traverseContextPool ,他是不是已經存在的一個節點,若是有的話,從這個 traverseContextPool 裏面 pop 一個,pop後,再把傳入進來的直接掛載到他上面,沒有作任何其餘的操做,其實就是用來記錄的對象,若是沒有,就 return 一個新的對象,這有什麼意義呢?回過頭去看一下,後續兩個方法調用了 traverseContext, 最後一句話代碼是 releaseTraverseContext(traverseContext) , 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); } }
咱們發現,他就是一個把 traverseContext 這個對象的內容都給清空了,而後判斷 traverseContextPool 是否小於 POOL_SIZE 這個最大的限制大小,就是10,若是沒有大於,就往裏面 push 這個對象。那這有什麼意義呢?這就是一個很簡單的一個對象池的一個概念,就是我這個 map function 極可能是我常常要調用的方法,若是他展開層數比較多,那麼我這個 traverseContextPool 聲明的對象也會比較多,若是這個聲明的對象比較多,我每次調用 map functioin, 都要聲明這麼多對象,而後調用完了以後,又要把這麼多對象釋放,這實際上是一個很是消耗性能的一個操做,由於一個聲明對象,和一個刪除對象,那麼他極可能會形成內存抖動的問題,而後讓咱們總體看到的瀏覽器內頁面的性能會比較差,因此在這裏他設置了這麼一個 traverseContextPool 。而後總共的長度給他一個10,而後是個漸進的過程,一開始他是一個空數組,隨着對象一個個建立,他會把他推動去,而後又把他拿出來複用,由於咱們知道 js 是一個單線程的咱們在執行某一個 props.children 的時候,能夠複用空間。而後咱們繼續看 traverseAllChildren 性能
function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; } return traverseAllChildrenImpl(children, '', callback, traverseContext); }
這個方法其實沒有什麼特別的東西,而後他 return 的實際上是一個 traverseAllChildrenImpl。this
function traverseAllChildrenImpl( children, nameSoFar, callback, traverseContext, ) { const type = typeof children; if (type === 'undefined' || type === 'boolean') { // All of the above are perceived as null. children = null; } let 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; } } } 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. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar, ); return 1; } let child; let nextName; let subtreeCount = 0; // Count of children found in the current subtree. const nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; if (Array.isArray(children)) { for (let i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext, ); } } else { const iteratorFn = getIteratorFn(children); if (typeof iteratorFn === 'function') { if (__DEV__) { // Warn about using Maps as children if (iteratorFn === children.entries) { warning( didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.', ); didWarnAboutMaps = true; } } const iterator = iteratorFn.call(children); let step; let 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') { let addendum = ''; if (__DEV__) { addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum(); } const childrenString = '' + children; invariant( false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum, ); } } return subtreeCount; }
而後這個方法就是一個重點了,若是這裏的 children 是一個單個的節點,他不是一個數組,那麼這個時候他會進入這裏面的判斷,是undefined, string , number , object 等等。object 是一個 REACT_ELEMENT_TYPE 或者 REACT_PORTAL_TYPE 。他們都是合理的 react 能夠渲染的節點,而後直接調用 invokeCallback 賦值爲 true, invokeCallback 爲 true 是,直接調用 callback,這裏的 callback 是傳入的 mapSingleChildIntoContext 這樣的一個方法,他們都有一個共同的特色,就是他們不是數組或者可遍歷的對象,因此他們是單個節點,因此對於單個節點,就能夠直接調用 callback。若是是個數組怎麼辦,若是是個數組,他就會去循環遍歷這個數組,而後他再調用他自身,而後再把這個 child 傳進去,這就是一個遞歸的過程,直到單個的時候,再去調用這個 callback,那麼這個 callback 是什麼呢, mapSingleChildIntoContextspa
function mapSingleChildIntoContext(bookKeeping, child, childKey) { const {result, keyPrefix, func, context} = bookKeeping; let mappedChild = func.call(context, child, bookKeeping.count++); if (Array.isArray(mappedChild)) { mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => 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 keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey, ); } result.push(mappedChild); } }
這個方法裏面作了什麼呢,傳入了三個參數,第一個參數就是 bookKeeping, 也就是 traverseContext,也就是 pool裏面的東西。第二個參數 child 就是 traverseAllChildrenImpl 裏面的直到單個的 children, childKey 就是一個映射關係,這個能夠忽略。
export function cloneAndReplaceKey(oldElement, newKey) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; }
這裏 cloneAndReplaceKey 也很是簡單,他其實就是 return 了一個新的 react element。除了一個 newKey 以外,其餘都是在原來的 react element 的基礎上返回
function toArray(children) { const result = []; mapIntoWithKeyPrefixInternal(children, result, null, child => child); return result; }
toArray就比較簡單了,只是 mapChildren 裏面的一層。
function onlyChild(children) { invariant( isValidElement(children), 'React.Children.only expected to receive a single React element child.', ); return children; }
onlyChild 其實就是判斷是不是單個合理的 react element節點 。