本文阅读react16.8.6版本ReactChildren.js文件。下面从mapChildren函数入手,给出React.Children.map的流程图。
请参考React源码阅读本文。
mapChildren遍历每个子节点调用函数并摊平子节点。
摊平子节点,即,把['a', ['b'], [['c'], 'd']]摊平成['a', 'b', 'c', 'd']
如果我们想自己实现摊平功能,我们会怎么写这个函数:
很简单的功能,为什么React写得那么复杂?我们来看下traverseAllChildren到底做了些什么。
根据我们画出的流程图,我们可以知道,React流程图红框中的内容对应我们蓝框中的代码。
(代码写多了,疏于言表?,用框框箭头代码示意,你们可以意会吧?♂️)
这么看,貌似挺简单的。但是,我们要知道,React是要把执行用户输入的func(即React.Children.map(this.props.children, c=>[c,[c]])中的c=>[c,[c]]函数)后所得到的mappedChild进行平铺。所以我们修改myTraverseAllChildren函数。如下。
现在的myTraverseAllChildren和我们先前写的有啥区别?
区别在于:先前写的,是将形参arr平铺到result中;而现在写的,是将形参arr的每个元素经过func处理后得到的新值(该值可能是任何类型)平铺到result中。
现在我们的myMapSingleEle函数对应React的mapSingleChildIntoContext。我们将两者进行对比,找出mapSingleChildIntoContext中逻辑和我们不一样的地方,并思考为什么。
我们发现,我们的myMapSingleEle函数和React的mapSingleChildIntoContext不同处在于:①当判断mappedSth为数组的时候,我们调用myTraverseAllChildren,而React调用一开始的mapIntoWithKeyPrefixInternal。②当mappedSth不为空时,mapSingleChildIntoContext还需判断是否是有效元素,如果是的话,就执行cloneAndReplaceKey。
第二点我们知道React是想要克隆并替换Key,使相同child副本的key均不同。
如:console.log(React.Children.map(this.props.children, c => [[c,c],c]))
控制台输出:
那么,为什么React调用之前的mapIntoWithKeyPrefixInternal,而不是traverseAllChildrenImpl?cloneAndReplaceKey函数看名称的意思是克隆并替换Key,那为什么要克隆并替换Key呢?
先看第一个问题。我们看mapIntoWithKeyPrefixInternal函数和traverseAllChildrenImpl函数有什么不同。
回头看我们流程图,我们发现,mapIntoWithKeyPrefixInternal包含traverseAllChildren,包含traverseAllChildrenImpl。主要就是比traverseAllChildrenImpl多了从对象重用池获取traverseContext和释放traverseContext的逻辑。我们思考,这个逻辑是干嘛的?可不可以不要这个逻辑?
为了看懂这个逻辑,我们又画了一个图。
如上图所给出的示例,如果没有这个逻辑,代码将开辟5个这样子的内存空间,但有了对象重用池,我们只开辟了3个空间。既节省了新开辟空间所需的时间,又节省了内存空间占用。