上一篇文章说了React的一部分基础API,今天这一篇文章说一下React.children前端
在React.js中,摘取出Children,其中罗列了React.children的几个方法react
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
}
复制代码
有这样一段代码es6
import React from 'react';
function ChildrenDemo(props) {
console.log(props.children, 'props.children');
console.log(React.Children.map(props.children, item => item), 'map');
console.log(React.Children.map(props.children, item => [item, [item, item]]), 'map');
console.log(React.Children.forEach(props.children, item => item), 'forEach');
console.log(React.Children.forEach(props.children, item => [item, [item, item]]), 'forEach');
console.log(React.Children.toArray(props.children), 'toArray');
console.log(React.Children.count(props.children), 'count');
console.log(React.Children.only(props.children[0]), 'only');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
咱们看一下,控制台的输出结果:web
咱们看到ajax
看到这里,你们内心确定有一个疑问,为何会返回这些结果呢?下面,咱们经过分析源码来得到咱们想要的答案json
咱们经过断点的方式一步一步地分析源码流程,并在最后绘制出流程图,加深理解数组
PS:引用react的时候引入react打包以后的文件(react.development.js)bash
import React from './react.development.js';
复制代码
import React from './react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.map(props.children, item => item), 'map');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
在react.development.js中,找到关于map方法的全部函数,在须要的地方打上断点,咱们看它是如何执行的。闭包
经过断点,map方法先执行mapChildren函数app
function mapChildren(children, func, context) {
//判断传入的children是否为null
if (children == null) {
// 为null,直接返回children
return children;
}
// 不为null,定义一个result
var result = [];
// 调用函数,并传入相应的五个参数
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
// 返回result
return result;
}
复制代码
// 方法接收五个参数
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context){
// 定义escapedPrefix,方便后面传参
var escapedPrefix = '';
// 判断传入的参数prefix不为null
if (prefix != null) {
// 若是prefix不为空,则调用escapeUserProvidedKey方法,传入prefix,在得到的结果后加上'/'
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
// 调用getPooledTraverseContext方法传入四个参数,将得到的结果赋值为traverseContext,方便为下面函数传参
var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);
// 调用traverseAllChildren方法,传入三个参数,其中mapSingleChildIntoContext是一个函数,这个函数的做用就是将嵌套的数组展开
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
// 调用releaseTraverseContext方法,传入参数
releaseTraverseContext(traverseContext);
}
复制代码
const userProvidedKeyEscapeRegex = /\/+/g;
// 匹配连续的'\'并替换为'$&/'
function escapeUserProvidedKey(text) {
return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
}
复制代码
该方法的主要做用是:建立一个对象池,复用Object,从而减小不少对象建立带来的内存占用和gc(垃圾回收)的损耗
// 这里定义了一个size为10的缓冲池
const POOL_SIZE = 10;
var traverseContextPool = [];
function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) {
if (traverseContextPool.length) { // 若是缓冲池中有值,则取出一个值使用
var 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
};
}
}
复制代码
上面的两种状况的返回值就是调用函数时接收的变量值。
// 函数传入了三个参数,第一个参数时children,第二个参数是一个mapSingleChildIntoContext函数,第三个参数时咱们通过上面方法返回的值
function traverseAllChildren(children, callback, traverseContext) {
// 假设子节点为空,直接返回0
if (children == null) {
return 0;
}
// 不然调用traverseAllChildrenImpl函数
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
// mapSingleChildIntoContext函数
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} = bookKeeping;
let mappedChild = func.call(context, child, bookKeeping.count++);
if (Array.isArray(mappedChild)) {
// 判断mappedChild是否是一个数组,若是是,再次调用mapIntoWithKeyPrefixInternal函数
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if (mappedChild != null) {// 若是不是数组,而且mappedChild部位null
// 判断mappedChild在isValidElement函数中的返回值是否是true,是才能够调用cloneAndReplaceKey方法,传入了mappedChild节点的key值
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中加入处理好的mappedChild节点,result是咱们在控制台打印出来的值
result.push(mappedChild);
}
}
复制代码
// 函数接收了四个参数
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
// 首先会判断children的类型
var type = typeof children;
// 若是类型为undefined或者是布尔类型,让children为null
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
// 定义一个布尔类型变量,用来判断是否要调用传进来的callback
var invokeCallback = false;
if (children === null) { // 假如子节点为null,让标识变量变为true-
invokeCallback = true;
} else { // 假如与上相反,会判断type的具体类型
switch (type) {
case 'string':
case 'number': // 是数字,将标识变为true
invokeCallback = true;
break;
case 'object': // 是object,继续判断子节点中的$$typeof
switch (children.$$typeof) {
// 是'REACT_ELEMENT_TYPE'类型,不作任何处理
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE: // 是REACT_PORTAL_TYPE将标志改成true
invokeCallback = true;
}
}
}
// 假如invokeCallback为true,会调用传进来的callback,也就是mapSingleChildIntoContext函数,这里会返回新的参数
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.
// SEPARATOR是key最开始有的'.',这里是当传入的nameSoFar为空时,要调用getComponentKey方法,
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
return 1;
}
var child = void 0;
var nextName = void 0;
var subtreeCount = 0; // Count of children found in the current subtree.
// 定义一个nextNamePrefix,这里是第二层子节点的key值处理,SUBSEPARATOR初值为':'
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// 判断children是否是一个数组
if (Array.isArray(children)) { // 是数组,会循环子节点
for (var i = 0; i < children.length; i++) {
child = children[i];
// 这里调用了getComponentKey方法,处理节点的key值
nextName = nextNamePrefix + getComponentKey(child, i);
// 统计子节点个数,会继续调用traverseAllChildrenImpl,此次给函数传递的是子节点,也就是json类型的数据,运用了递归的思想
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 = void 0;
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;
(function () {
{
{
throw ReactError(Error('Objects are not valid as a React child (found: ' + (childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString) + ').' + addendum));
}
}
})();
}
}
// 返回了子节点数量
return subtreeCount;
}
复制代码
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);
}
}
复制代码
而后返回的result就是咱们在控制台输出的结果。
import React from '../react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.map(props.children, item => [item, [item, item]]), 'map');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
这时候,传入的展开数组函数方法变得不同了,咱们仍是按照上述的方法再走一遍流程,看看二者之间有什么不一样和相同。
咱们经过debugger发现,函数的执行流程和上述并无什么不用,只是某几个函数执行的次数发生了变化。
咱们知道在es6语法中map和forEach方法都是遍历一个数组,在这里边也是一样的,只是map方法有返回,而forEach方法没有,因此这篇文章再也不对forEach进行讲解。
import React from '../react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.toArray(props.children), 'toArray');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
经过的debugger,咱们发现它接下来要走的流程和map函数是同样的。
import React from '../react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.count(props.children), 'count');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
count的入口函数
function countChildren(children) {
return traverseAllChildren(children, function () {
return null;
}, null);
}
复制代码
它调用了展平数组的函数,咱们在上面写源码过程的时候,在traverseAllChildrenImpl函数中计算了节点的个数。
import React from '../react.development.js';
function ChildrenDemo(props) {
console.log(React.Children.only(props.children[0]), 'only');
return props.children
}
export default () => (
<ChildrenDemo>
<div>1</div>
<span>2</span>
</ChildrenDemo>
)
复制代码
在上面,咱们在控制台打印的结果是一个json数据,包括了咱们传进去节点的信息。下面,咱们来看一下,它的源码部分
function onlyChild(children) {
// 一个闭包函数,假设传入的不是一个节点,那么会抛出异常
(function () {
if (!isValidElement(children)) {
{
throw ReactError(Error('React.Children.only expected to receive a single React element child.'));
}
}
})();
// 若是没问题,就会返回传过来的节点信息
return children;
}
复制代码
咱们把React.children的五个方法都一一分析完毕,咱们发现除了onlyChild的方法都执行了共同的方法。因此,为了能更加清晰和更好地的理解React.children方法的流程,咱们画一张流程图感觉一下(这里只画map方法的)
function mapChildren(array) {
var result = [];
for(var i = 0;i < array.length; i++) {
if (Array.isArray(array[i])) {
// 递归思想
result = result.concat(mapChildren(array[i]))
} else {
result.push(array[i])
}
}
return result;
}
const result = mapChildren([1,[1,2,[3,4,5]]])
console.log(result); // [1,1,2,3,4,5]
复制代码
function mapChildren(array) {
while(array.some(item => Array.isArray(item)))
array = [].concat(...array);
return array
}
const result = mapChildren([1,[1,2,[3,4,5]]])
console.log(result); // [1,1,2,3,4,5]
复制代码
React.children的源码至此所有分析完毕,咱们要学习到框架的思想,拓展咱们的思惟,将这些思想运用到实战中,而且改善编码习惯,写出高质量的代码~
上述文章若有不对之处,还请你们指点出来,咱们共同窗习,共同进步~
最后,分享一下个人公众号【web前端日记】,关注后有资料能够领取(通常人我不告诉哦)~