ReactChildren
React.Children
提供了用于處理 this.props.children
不透明數(shù)據(jù)結(jié)構(gòu)的實(shí)用方法逝段。
React.Children.map
React.Children.map(children, function[(thisArg)])
在 children
里的每個(gè)直接子節(jié)點(diǎn)上調(diào)用一個(gè)函數(shù),并將 this
設(shè)置為 thisArg
豁生。如果 children
是一個(gè)數(shù)組酸纲,它將被遍歷并為數(shù)組中的每個(gè)子節(jié)點(diǎn)調(diào)用該函數(shù)捣鲸。如果子節(jié)點(diǎn)為 null
或是 undefined
,則此方法將返回 null
或是 undefined
闽坡,而不會(huì)返回?cái)?shù)組栽惶。
注意
如果
children
是一個(gè)Fragment
對(duì)象,它將被視為單一子節(jié)點(diǎn)的情況處理疾嗅,而不會(huì)被遍歷外厂。
React.Children.forEach
React.Children.forEach(children, function[(thisArg)])
與 React.Children.map()
類似,但它不會(huì)返回一個(gè)數(shù)組代承。
React.Children.count
React.Children.count(children)
返回 children
中的組件總數(shù)量汁蝶,等同于通過(guò) map
或 forEach
調(diào)用回調(diào)函數(shù)的次數(shù)。
React.Children.only
React.Children.only(children)
驗(yàn)證 children
是否只有一個(gè)子節(jié)點(diǎn)(一個(gè) React 元素)论悴,如果有則返回它掖棉,否則此方法會(huì)拋出錯(cuò)誤。
注意:
React.Children.only()
不接受React.Children.map()
的返回值膀估,因?yàn)樗且粋€(gè)數(shù)組而并不是 React 元素啊片。
React.Children.toArray
React.Children.toArray(children)
將 children
這個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)以數(shù)組的方式扁平展開(kāi)并返回,并為每個(gè)子節(jié)點(diǎn)分配一個(gè) key玖像。當(dāng)你想要在渲染函數(shù)中操作子節(jié)點(diǎn)的集合時(shí)紫谷,它會(huì)非常實(shí)用,特別是當(dāng)你想要在向下傳遞 this.props.children
之前對(duì)內(nèi)容重新排序或獲取子集時(shí)捐寥。
注意:
React.Children.toArray()
在拉平展開(kāi)子節(jié)點(diǎn)列表時(shí)笤昨,更改 key 值以保留嵌套數(shù)組的語(yǔ)義。也就是說(shuō)握恳,toArray
會(huì)為返回?cái)?shù)組中的每個(gè) key 添加前綴瞒窒,以使得每個(gè)元素 key 的范圍都限定在此函數(shù)入?yún)?shù)組的對(duì)象內(nèi)。
源碼解析
some utils:
const SEPARATOR = '.';
const SUBSEPARATOR = ':';
/**
* Escape and wrap key so it is safe to use as a reactid
*
* @param {string} key to be escaped.
* @return {string} the escaped key.
*/
function escape(key) {
const escapeRegex = /[=:]/g;
const escaperLookup = {
'=': '=0',
':': '=2',
};
const escapedString = ('' + key).replace(escapeRegex, function(match) {
return escaperLookup[match];
});
return '$' + escapedString;
}
/**
* TODO: Test that a single child and an array with one item have the same key
* pattern.
*/
let didWarnAboutMaps = false;
const userProvidedKeyEscapeRegex = /\/+/g;
function escapeUserProvidedKey(text) {
return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
}
const POOL_SIZE = 10;
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,
};
}
}
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);
}
}
-
SEPARATOR
:react key 的分隔符 -
SUBSEPARATOR
: react sub-children key 的分隔符 -
escape
和escapeUserProvidedKey
: 轉(zhuǎn)譯key -
getPooledTraverseContext
: 如果traverseContextPool
中有元素乡洼,就從traverseContextPool
中拿出一個(gè)元素崇裁,這樣做的好處是可以減少內(nèi)存的占用匕坯。
否則就創(chuàng)建一個(gè)新的元素,來(lái)存儲(chǔ)context的信息:result
,keyPrefix
,func
,context
,count
; -
releaseTraverseContext
:釋放并初始化某個(gè)context拔稳,如果traverseContextPool
的大小<10的話葛峻,這個(gè)context會(huì)被push進(jìn)去。
關(guān)鍵API和函數(shù):
traverseAllChildrenImpl()
/**
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
* @param {!function} callback Callback to invoke with each child found.
* @param {?*} traverseContext Used to pass information throughout the traversal
* process.
* @return {!number} The number of children in this subtree.
*/
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;
}
在traverseAllChildrenImpl
函數(shù)中巴比,首先對(duì)children
的類型做了判斷术奖。
- 如果不是數(shù)組、map轻绞、普通對(duì)象類型采记,就直接執(zhí)行傳入的
callback
函數(shù),并返回1政勃。其中callback
函數(shù)執(zhí)行的時(shí)候傳入的key
是通過(guò)getComponentKey
并轉(zhuǎn)譯得到的唧龄。 - 如果
children
是數(shù)組對(duì)象,就遞歸調(diào)用自己奸远,且在遞歸調(diào)用中选侨,分隔符變成了SUBSEPARATOR
; - 如果是Map對(duì)象,React會(huì)拋出警告日志然走,因?yàn)镸ap對(duì)象的迭代器是不保證順序的。
- 如果是普通的Object對(duì)象戏挡,就會(huì)拋出錯(cuò)誤芍瑞,并不會(huì)執(zhí)行下去。
forEach
/**
* Traverses children that are typically specified as `props.children`, but
* might also be specified through attributes:
*
* - `traverseAllChildren(this.props.children, ...)`
* - `traverseAllChildren(this.props.leftPanelChildren, ...)`
*
* The `traverseContext` is an optional argument that is passed through the
* entire traversal. It can be used to store accumulations or anything else that
* the callback might find relevant.
*
* @param {?*} children Children tree object.
* @param {!function} callback To invoke upon traversing each child.
* @param {?*} traverseContext Context for traversal.
* @return {!number} The number of children in this subtree.
*/
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
function forEachSingleChild(bookKeeping, child, name) {
const {func, context} = bookKeeping;
func.call(context, child, bookKeeping.count++);
}
/**
* Iterates through children that are typically specified as `props.children`.
*
* See https://reactjs.org/docs/react-api.html#reactchildrenforeach
*
* The provided forEachFunc(child, index) will be called for each
* leaf child.
*
* @param {?*} children Children tree container.
* @param {function(*, int)} forEachFunc
* @param {*} forEachContext Context for forEachContext.
*/
function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
const traverseContext = getPooledTraverseContext(
null,
null,
forEachFunc,
forEachContext,
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}
forEachChildren
即是forEach
API褐墅,在這個(gè)函數(shù)中拆檬,會(huì)拿到一個(gè)初始化后的context
,并在這個(gè)context
中綁定開(kāi)發(fā)者自定義的forEachFunc
和forEachContext
妥凳,之后會(huì)執(zhí)行traverseAllChildren
竟贯,traverseAllChildren
執(zhí)行完畢后,會(huì)釋放context
逝钥。
map
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);
}
}
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);
}
/**
* Maps children that are typically specified as `props.children`.
*
* See https://reactjs.org/docs/react-api.html#reactchildrenmap
*
* The provided mapFunction(child, key, index) will be called for each
* leaf child.
*
* @param {?*} children Children tree container.
* @param {function(*, int)} func The map function.
* @param {*} context Context for mapFunction.
* @return {object} Object containing the ordered map of results.
*/
function mapChildren(children, func, context) {
if (children == null) {
return children;
}
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
mapChildren
即是map
API屑那,它會(huì)調(diào)用mapIntoWithKeyPrefixInternal
,并把返回的結(jié)果存入result并返回艘款。與forEach不同的是持际,mapChildren會(huì)為每一個(gè)component創(chuàng)建一個(gè)新的key。