一,React.Children是什么?

是为了处理this.props.children(this.props.children表示所有组件的子节点)这个属性提供的工具,是顶层的api之一

二,React.Children.map的结构及举例

 结构:React.Children.map(object children,function fn [, object context])

举例如下:

 
   //举例:(<span>1</span>和 <span>2</span>):多个子节点情况  this.props.children:数据类型为数组  React.Children.map用来遍历子节点
class ChildrenDemo extends React.Component {
render() {
return (
<ol>
{
React.Children.map(this.props.children, (child)=> {//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变成一个一维数组

三、React.Children.map()中map方法的源码解析
源码中: Children{map: mapChildren} //所以真正的名字是mapChildren
 mapChildren()
    //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;
}
 解析:首先定义一个result,然后执行一个函数以后,返回这个result
 
mapIntoWithKeyPrefixInternal()
    /*
第一次: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, '$&/');
}
 解析:react对key定义的规则 如果字符串有多个/的话在字符串后面加/
 
getPooledTraverseContext()
 //创建一个对象池 复用对象 从而减少对象带来的内存暂用 和性能消耗
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
};
}
}

 解析:创建一个对象池 复用对象 从而减少对象带来的内存暂用 和性能消耗

  traverseAllChildren()
   //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单个ReactElement/PortalElement
//递归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;
}

解析:核心递归函数 目的为展平数组,对于可以循环的children 都会重复调用traverseAllChildrenImpl 直到一个节点 然后调用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

流程图如下

 

React源码解析之React.Children.map()(五)的更多相关文章

  1. [源码解析]为什么mapPartition比map更高效

    [源码解析]为什么mapPartition比map更高效 目录 [源码解析]为什么mapPartition比map更高效 0x00 摘要 0x01 map vs mapPartition 1.1 ma ...

  2. React源码解析:setState

    先来几个例子热热身: ......... constructor(props){ super(props); this.state = { index: 0 } } componentDidMount ...

  3. React源码解析:ReactElement

    ReactElement算是React源码中比较简单的部分了,直接看源码: var ReactElement = function(type, key, ref, self, source, owne ...

  4. React源码解析——ReactAPI

    一.API背景 api的具体转化关系 可以通过到https://babeljs.io/repl/网站去将我们创建的Jsx进行实时的转译 const React = { Children: { map, ...

  5. React源码解析-Virtual DOM解析

    前言:最近一直在研究React,看了陈屹先生所著的深入React技术栈,以及自己使用了这么长时间.对React应该说有比较深的理解了,正好前阵子也把两本关于前端设计模式的书看完了,总感觉有一种知识错综 ...

  6. React源码解析——创建更新过程

    一.ReactDOM.render 创建ReactRoot,并且根据情况调用root.legacy_renderSubtreeIntoContainer或者root.render,前者是遗留的 API ...

  7. jQuery 源码解析(六) $.each和$.map的区别

    $.each主要是用来遍历数组或对象的,例如: var arr=[11,12,13,14]; $.each(arr,function(element,index){ //遍历arr数组 console ...

  8. React躬行记(16)——React源码分析

    React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...

  9. 源码解读 Golang 的 sync.Map 实现原理

    简介 Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent ...

随机推荐

  1. 常用的H5代码

    1.返回上一页第一次在手机端用到返回上一页的时候,只写了window.history.go(-1):这一句.但是只在安卓手机有效果,兼容苹果手机需要在跳转代码后加上return false:这句.跳转 ...

  2. (转)最小Hash和局部敏感Hash

    转自:http://www.07net01.com/2015/08/907327.html 在数据挖掘中,有一个比较基本的问题,就是比较两个集合的相似度.关于这个问题,最笨的方法就是用一个两重循环来遍 ...

  3. jsp中引用的jstl 和fmt标签-详解

    JSTL 核心标签库标签共有13个,功能上分为4类: 1.表达式控制标签:out.set.remove.catch 2.流程控制标签:if.choose.when.otherwise 3.循环标签:f ...

  4. String.format()的用法记录

    stirng.format("redirect:http://%s:%s%s",ip,port,path) 相当于http://localhost:8080/user/list S ...

  5. AE 打开Shp文件

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. 逗号运算符与括号 - C语言

    例1 int x; int a=(x=2),12;// 赋值优先级高于逗号,相当于a=x=2,12是多余的 printf("a=%d",a); 结果:a=2 例2 int x; i ...

  7. promise的连缀写法

    promise的连缀写法 以上写法相当于写了两个实例 promise.all() 1. promise.all() all这个方法是 promise 构造函数的成员不是实例对象成员,这个方法接受一个参 ...

  8. 巨杉数据库入选年度Gartner Peer Insights报告,获得市场高度评价

    Gartner Peer Insights 年度评选结果于近日出炉,在数据库管理系统市场报告中,巨杉数据库获得了总平均分4.7(满分5分)的成绩,在众多国际厂商中位居第三,是国内唯一一家入选的数据库厂 ...

  9. SQL操作DBF

    --从SQL Server查询器预览dBase 文件中数据select * from openrowset('MICROSOFT.ACE.OLEDB.12.0','dBase 5.0;database ...

  10. 部署prerender服务器

    // 安装 git sudo apt-get install git sudo apt-get install curl // 请先确认服务器是否安装了curl 如果已经安装跳过即可 // 安装 no ...