React.Children 常用API源碼解讀
我們在寫React組件時(shí)候脖阵,經(jīng)常會有通過this.props.children傳遞給組件進(jìn)行渲染包裹頁面,通常會通過this.props.children.map()來遍歷渲染轧邪,其實(shí)React有提供一個(gè)API來做這里拖吼。也就是我們今天要說的React.Children.map
問題
- 直接數(shù)組的map和React.Children.map有什么區(qū)別
- React.Children.map和React.Children.forEach有什么區(qū)別
首先鼻疮,我們寫個(gè)例子
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來進(jìn)行處理
var Children = {
map: mapChildren,
forEach: forEachChildren,
count: countChildren,
toArray: toArray,
only: onlyChild
};
React的Children是個(gè)對象假勿,里面有map借嗽、forEach等等方法,map對應(yīng)的就是mapChildren函數(shù)转培,我們看看這個(gè)函數(shù)做了什么
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 - 我們傳遞進(jìn)來的ComWarp包裹的子內(nèi)容是個(gè)數(shù)組恶导,里面是每個(gè)div的對象($$type是react.element)
- 創(chuàng)建了一個(gè)空的數(shù)組result最后返回,我們繼續(xù)看看mapIntoArray函數(shù),這個(gè)函數(shù)比較長浸须,我們分幾個(gè)部分來說
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是一個(gè)數(shù)組,那也就是object類型删窒,走下switch object,發(fā)現(xiàn)沒有case符合裂垦,因?yàn)橹皇莻€(gè)純數(shù)組而已沒有$$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." );
}
}
}
}
這里其實(shí)就會判斷children是個(gè)數(shù)組的話(成立)肌索,就會遍歷children蕉拢,得到每個(gè)子級繼續(xù)進(jìn)行mapIntoArray(看到?jīng)]遞歸函數(shù))
nextName定義一個(gè)常量+本身的key,用于動態(tài)生成key給沒個(gè)元素的props添加后續(xù)方法cloneAndReplaceKey
i=0,child的子級就是具體某一個(gè)react元素,繼續(xù)執(zhí)行mapIntoArray,此時(shí)我們再返回到第一部分诚亚,子級就是一個(gè)object,且是個(gè)react元素$$typeof是REACT_ELEMENT_TYPE, invokeCallback設(shè)置true,我們繼續(xù)看true后會執(zhí)行什么
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;
}
執(zhí)行了我們傳遞過來的callback函數(shù)也就是(c) => ()),執(zhí)行完后判斷返回的mappedChild的類型晕换,如果不是數(shù)組且不是null,就會把mappedChild放入到array(一開始我們定義的空數(shù)組)
如果mappedChild是數(shù)組,也就是我們傳遞的(c) =>[c,c],那就會繼續(xù)mapIntoArray站宗,此時(shí)mapIntoArray的chilren是返回的[c闸准,c]的數(shù)組,callback改造成c=>c份乒,而不是開始的(c) =>[c,c]
最后往復(fù)遞歸形成新的一個(gè)數(shù)組result
流程圖
React.Children.forEach
function forEachChildren(children, forEachFunc, forEachContext) {
mapChildren(children, function () {
forEachFunc.apply(this, arguments); // Don't return anything.
}, forEachContext);
}
調(diào)用的也是mapChildren函數(shù)恕汇,區(qū)別于map是map會返回一個(gè)新的數(shù)組,而forEach只是處理forEachFunc不返回
toArray
function toArray(children) {
return mapChildren(children, function (child) {
return child;
}) || [];
}
調(diào)用mapChildren或辖,返回處理好的數(shù)組
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元素