今昔何年 發表於 2019-11-30 17:32:00

React源码 React.Children

<div>children是什么意思呢?就是我们拿到组件内部的props的时候,有props.children这么一个属性,大部分情况下,我们直接把&nbsp;props.children&nbsp;渲染到&nbsp;JSX&nbsp;里面就可以了。很少有情况我们需要去操作这个&nbsp;children&nbsp;。&nbsp;但是如果一旦你需要去操作这个&nbsp;children&nbsp;。我们推荐使用&nbsp;React.children&nbsp;的&nbsp;api&nbsp;,&nbsp;而不是直接去操作他。</div>
<p>&nbsp;</p>
<div>虽然说我们大部分情况下拿到的&nbsp;children&nbsp;是合理的&nbsp;react&nbsp;element&nbsp;或者是一个数组,但是&nbsp;React&nbsp;有提供&nbsp;api&nbsp;去操作他,那么他一定是有一个合理的原因的。</div>
<p>&nbsp;</p>
<div>打开&nbsp;React.js&nbsp;源码,找到&nbsp;children</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Children: {
    map,
    forEach,
    count,
    toArray,
    only,
}</span></pre>
</div>
<p>这个&nbsp;children&nbsp;是个对象,这个对象里面有&nbsp;5&nbsp;个属性,这个&nbsp;5&nbsp;个属性看上去就跟我们数组的操作非常的像,前两个是最重要的,也就是&nbsp;map&nbsp;和&nbsp;forEach&nbsp;,就相当于一个数组的&nbsp;map&nbsp;和&nbsp;forEach&nbsp;方法,我们可以理解为意义是一样的,但是实际的操作跟数组的&nbsp;map&nbsp;和&nbsp;forEach&nbsp;会有一点的区别。&nbsp;这里&nbsp;map&nbsp;是所有逻辑里面最复杂的一个。而&nbsp;map&nbsp;和&nbsp;forEach&nbsp;是差不多的,他们唯一的区别是一个有返回,一个没有返回,map&nbsp;是通过我们传入一个方法之后,调用出来之后,返回的一个新的数组,而&nbsp;forEach&nbsp;是返回原数组的。</p>
</div>
<p>&nbsp;</p>
<div>我们先看一个&nbsp;demo,</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre>import React from 'react'

<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> ChildrenDemo(props) {
console.log(props.children)
console.log(React.Children.map(props.children, c </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> ]))
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> props.children
}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> () =&gt;<span style="color: rgba(0, 0, 0, 1)"> (
</span>&lt;ChildrenDemo&gt;
    &lt;span&gt;1&lt;/span&gt;
    &lt;span&gt;2&lt;/span&gt;
&lt;/ChildrenDemo&gt;
)</pre>
</div>
<p>我们创建了一个组件,叫&nbsp;ChildrenDemo&nbsp;,然后里面包裹了两个&nbsp;span&nbsp;作为他的&nbsp;children&nbsp;,然后在他的&nbsp;props.children&nbsp;里面就可以拿到,第一个打印出来的就是&nbsp;props.children,我们看到就是两个&nbsp;element&nbsp;节点。&nbsp;就跟之前的&nbsp;React&nbsp;Element&nbsp;属性是一样的。第二个是通过&nbsp;React.Children.map&nbsp;这个&nbsp;api&nbsp;,然后传入&nbsp;props.children&nbsp;。&nbsp;并且传入了一个&nbsp;callback&nbsp;,这个&nbsp;callback&nbsp;返回的是一个嵌套两层的数组,可能比较迷茫,可以先看成&nbsp;c&nbsp;=&gt;&nbsp;,然后打印出来的children分别是1,1,2,2,我们是否可以理解为他最终返回的是一个展开的,因为这总共是两个节点,props.children&nbsp;是两个节点,每个节点通过&nbsp;map&nbsp;function&nbsp;之后返回的是一个数组,然后&nbsp;React.Children&nbsp;把他给展开了,然后就变成了一个一维数组,本来是返回一个二维数组,然后这个数组里面有两个一维数组。</p>
</div>
<p>&nbsp;</p>
<div>也就是这里不管嵌套几层,返回的一维数组。这就是&nbsp;React.Children.map&nbsp;跟&nbsp;数组&nbsp;.map&nbsp;的一个本质的区别,数组map第一个参数是子元素,这里的第一个参数是要遍历的数组,然后返回一个一维数组。我们接下来看下他的源码是如何做的。打开&nbsp;ReactChildren.js&nbsp;。翻到最下面,看到</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};</span></pre>
</div>
<p>mapChildren&nbsp;as&nbsp;map,这里&nbsp;export&nbsp;出去的&nbsp;map&nbsp;,在里面就是&nbsp;mapChildren&nbsp;这个方法</p>
</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> mapChildren(children, func, context) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (children == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> children;
}
const result </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
mapIntoWithKeyPrefixInternal(children, result, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, func, context);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p>找到&nbsp;mapChildren&nbsp;这个方法,这里面先判断这个&nbsp;children&nbsp;是否等于&nbsp;null&nbsp;,如果等于&nbsp;null&nbsp;,就直接返回了。然后声明了一个&nbsp;result&nbsp;,最终&nbsp;return&nbsp;的也是这个&nbsp;result&nbsp;。这个时候我们再去看看&nbsp;forEachChildren</p>
</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> forEachChildren(children, forEachFunc, forEachContext) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (children == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> children;
}
const traverseContext </span>=<span style="color: rgba(0, 0, 0, 1)"> getPooledTraverseContext(
    </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">,
    forEachFunc,
    forEachContext,
);
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}</span></pre>
</div>
<p>这里面的&nbsp;forEachChildren&nbsp;没有result&nbsp;,没有返回值。这就是他们一个本质的区别。</p>
</div>
<p> <br><br></p>
<div>mapChildren&nbsp;里面调用了&nbsp;mapIntoWithKeyPrefixInternal,传入了&nbsp;children&nbsp;,&nbsp;result&nbsp;是我们刚刚声明的,第三个是&nbsp;null,&nbsp;function&nbsp;是我们传入的第二个参数,context&nbsp;就是&nbsp;this.object&nbsp;,一般我们不用他,就不管这个东西&nbsp;</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
let escapedPrefix </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (prefix != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    escapedPrefix </span>= escapeUserProvidedKey(prefix) + '/'<span style="color: rgba(0, 0, 0, 1)">;
}
const traverseContext </span>=<span style="color: rgba(0, 0, 0, 1)"> getPooledTraverseContext(
    array,
    escapedPrefix,
    func,
    context,
);
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
releaseTraverseContext(traverseContext);
}</span></pre>
</div>
</div>
<p>mapIntoWithKeyPrefixInternal&nbsp;里面我们看到他先处理了一下&nbsp;key,这个&nbsp;key&nbsp;先忽略,因为他就是一个字符串处理相关的东西,没有什么特别的,下面再调用这个三个函数,跟&nbsp;forEachChildren&nbsp;对比下,是差不多的。通过调用&nbsp;getPooledTraverseContext&nbsp;,然后去获取了一个&nbsp;traverseContext&nbsp;,这个东西有什么意义呢?我们直接看下这个方法&nbsp;</p>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre>const traverseContextPool =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (traverseContextPool.length) {
    const traverseContext </span>=<span style="color: rgba(0, 0, 0, 1)"> traverseContextPool.pop();
    traverseContext.result </span>=<span style="color: rgba(0, 0, 0, 1)"> mapResult;
    traverseContext.keyPrefix </span>=<span style="color: rgba(0, 0, 0, 1)"> keyPrefix;
    traverseContext.func </span>=<span style="color: rgba(0, 0, 0, 1)"> mapFunction;
    traverseContext.context </span>=<span style="color: rgba(0, 0, 0, 1)"> mapContext;
    traverseContext.count </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> traverseContext;
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
      result: mapResult,
      keyPrefix: keyPrefix,
      func: mapFunction,
      context: mapContext,
      count: </span>0<span style="color: rgba(0, 0, 0, 1)">,
    };
}
}</span></pre>
</div>
<p>我们看到这个方法其实没有什么特别的处理,他首先判断一下这个全局变量&nbsp;traverseContextPool&nbsp;,他是否是已经存在的一个节点,如果有的话,从这个&nbsp;traverseContextPool&nbsp;里面&nbsp;pop&nbsp;一个,pop后,再把传入进来的直接挂载到他上面,没有做任何其他的操作,其实就是用来记录的对象,如果没有,就&nbsp;return&nbsp;一个新的对象,这有什么意义呢?回过头去看一下,后续两个方法调用了&nbsp;traverseContext,&nbsp;最后一句话代码是&nbsp;releaseTraverseContext(traverseContext)&nbsp;,&nbsp;releaseTraverseContext&nbsp;他又是什么意思呢?</p>
</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> releaseTraverseContext(traverseContext) {
traverseContext.result </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
traverseContext.keyPrefix </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
traverseContext.func </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
traverseContext.context </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
traverseContext.count </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (traverseContextPool.length &lt;<span style="color: rgba(0, 0, 0, 1)"> POOL_SIZE) {
    traverseContextPool.push(traverseContext);
}
}</span></pre>
</div>
<p>我们发现,他就是一个把&nbsp;traverseContext&nbsp;这个对象的内容都给清空了,然后判断&nbsp;traverseContextPool&nbsp;是否小于&nbsp;POOL_SIZE&nbsp;这个最大的限制大小,就是10,如果没有大于,就往里面&nbsp;push&nbsp;这个对象。那这有什么意义呢?这就是一个很简单的一个对象池的一个概念,就是我这个&nbsp;map&nbsp;function&nbsp;很可能是我经常要调用的方法,如果他展开层数比较多,那么我这个&nbsp;traverseContextPool&nbsp;声明的对象也会比较多,如果这个声明的对象比较多,我每次调用&nbsp;map&nbsp;functioin,&nbsp;都要声明这么多对象,然后调用完了之后,又要把这么多对象释放,这其实是一个非常消耗性能的一个操作,因为一个声明对象,和一个删除对象,那么他很可能会造成内存抖动的问题,然后让我们整体看到的浏览器内页面的性能会比较差,所以在这里他设置了这么一个&nbsp;traverseContextPool&nbsp;。然后总共的长度给他一个10,然后是个渐进的过程,一开始他是一个空数组,随着对象一个个创建,他会把他推进去,然后又把他拿出来复用,因为我们知道&nbsp;js&nbsp;是一个单线程的我们在执行某一个&nbsp;props.children&nbsp;的时候,可以复用空间。然后我们继续看&nbsp;traverseAllChildren&nbsp;</p>
</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> traverseAllChildren(children, callback, traverseContext) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (children == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 0<span style="color: rgba(0, 0, 0, 1)">;
}


</span><span style="color: rgba(0, 0, 255, 1)">return</span> traverseAllChildrenImpl(children, ''<span style="color: rgba(0, 0, 0, 1)">, callback, traverseContext);
}</span></pre>
</div>
<p>这个方法其实没有什么特别的东西,然后他&nbsp;return&nbsp;的其实是一个&nbsp;traverseAllChildrenImpl。</p>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext,
) {
const type </span>= <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)"> children;


</span><span style="color: rgba(0, 0, 255, 1)">if</span> (type === 'undefined' || type === 'boolean'<span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> All of the above are perceived as null.</span>
    children = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
}


let invokeCallback </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;


</span><span style="color: rgba(0, 0, 255, 1)">if</span> (children === <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    invokeCallback </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (type) {
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'string'<span style="color: rgba(0, 0, 0, 1)">:
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'number'<span style="color: rgba(0, 0, 0, 1)">:
      invokeCallback </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">case</span> 'object'<span style="color: rgba(0, 0, 0, 1)">:
      </span><span style="color: rgba(0, 0, 255, 1)">switch</span> (children.$$<span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">) {
          </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> REACT_ELEMENT_TYPE:
          </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> REACT_PORTAL_TYPE:
            invokeCallback </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}


</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (invokeCallback) {
    callback(
      traverseContext,
      children,
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If it's the only child, treat the name as if it was wrapped in an array</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> so that it's consistent if the number of children grows.</span>
      nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0<span style="color: rgba(0, 0, 0, 1)">) : nameSoFar,
    );
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 1<span style="color: rgba(0, 0, 0, 1)">;
}


let child;
let nextName;
let subtreeCount </span>= 0; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Count of children found in the current subtree.</span>
const nextNamePrefix =<span style="color: rgba(0, 0, 0, 1)">
    nameSoFar </span>=== '' ? SEPARATOR : nameSoFar +<span style="color: rgba(0, 0, 0, 1)"> SUBSEPARATOR;


</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (Array.isArray(children)) {
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; children.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
      child </span>=<span style="color: rgba(0, 0, 0, 1)"> children;
      nextName </span>= nextNamePrefix +<span style="color: rgba(0, 0, 0, 1)"> getComponentKey(child, i);
      subtreeCount </span>+=<span style="color: rgba(0, 0, 0, 1)"> traverseAllChildrenImpl(
      child,
      nextName,
      callback,
      traverseContext,
      );
    }
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    const iteratorFn </span>=<span style="color: rgba(0, 0, 0, 1)"> getIteratorFn(children);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> iteratorFn === 'function'<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (__DEV__) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Warn about using Maps as children</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (iteratorFn ===<span style="color: rgba(0, 0, 0, 1)"> children.entries) {
          warning(
            didWarnAboutMaps,
            </span>'Using Maps as children is unsupported and will likely yield ' +
            'unexpected results. Convert it to a sequence/iterable of keyed ' +
            'ReactElements instead.'<span style="color: rgba(0, 0, 0, 1)">,
          );
          didWarnAboutMaps </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      }


      const iterator </span>=<span style="color: rgba(0, 0, 0, 1)"> iteratorFn.call(children);
      let step;
      let ii </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">while</span> (!(step =<span style="color: rgba(0, 0, 0, 1)"> iterator.next()).done) {
      child </span>=<span style="color: rgba(0, 0, 0, 1)"> step.value;
      nextName </span>= nextNamePrefix + getComponentKey(child, ii++<span style="color: rgba(0, 0, 0, 1)">);
      subtreeCount </span>+=<span style="color: rgba(0, 0, 0, 1)"> traverseAllChildrenImpl(
          child,
          nextName,
          callback,
          traverseContext,
      );
      }
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (type === 'object'<span style="color: rgba(0, 0, 0, 1)">) {
      let addendum </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (__DEV__) {
      addendum </span>=
          ' If you meant to render a collection of children, use an array ' +
          'instead.' +<span style="color: rgba(0, 0, 0, 1)">
          ReactDebugCurrentFrame.getStackAddendum();
      }
      const childrenString </span>= '' +<span style="color: rgba(0, 0, 0, 1)"> children;
      invariant(
      </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
      </span>'Objects are not valid as a React child (found: %s).%s'<span style="color: rgba(0, 0, 0, 1)">,
      childrenString </span>=== ''
          ? 'object with keys {' + Object.keys(children).join(', ') + '}'<span style="color: rgba(0, 0, 0, 1)">
          : childrenString,
      addendum,
      );
    }
}


</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> subtreeCount;
}</span></pre>
</div>
<p>然后这个方法就是一个重点了,如果这里的&nbsp;children&nbsp;是一个单个的节点,他不是一个数组,那么这个时候他会进入这里面的判断,是undefined,&nbsp;string&nbsp;,&nbsp;number&nbsp;,&nbsp;object&nbsp;等等。object&nbsp;是一个&nbsp;REACT_ELEMENT_TYPE&nbsp;或者&nbsp;REACT_PORTAL_TYPE&nbsp;。他们都是合理的&nbsp;react&nbsp;可以渲染的节点,然后直接调用&nbsp;invokeCallback&nbsp;赋值为&nbsp;true,&nbsp;invokeCallback&nbsp;为&nbsp;true&nbsp;是,直接调用&nbsp;callback,这里的&nbsp;callback&nbsp;是传入的&nbsp;mapSingleChildIntoContext&nbsp;这样的一个方法,他们都有一个共同的特点,就是他们不是数组或者可遍历的对象,所以他们是单个节点,所以对于单个节点,就可以直接调用&nbsp;callback。如果是个数组怎么办,如果是个数组,他就会去循环遍历这个数组,然后他再调用他自身,然后再把这个&nbsp;child&nbsp;传进去,这就是一个递归的过程,直到单个的时候,再去调用这个&nbsp;callback,那么这个&nbsp;callback&nbsp;是什么呢,&nbsp;mapSingleChildIntoContext</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} </span>=<span style="color: rgba(0, 0, 0, 1)"> bookKeeping;


let mappedChild </span>= func.call(context, child, bookKeeping.count++<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (Array.isArray(mappedChild)) {
    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> c);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (mappedChild != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isValidElement(mappedChild)) {
      mappedChild </span>=<span style="color: rgba(0, 0, 0, 1)"> cloneAndReplaceKey(
      mappedChild,
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Keep both the (mapped) and old keys if they differ, just as</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> traverseAllChildren used to do for objects as children</span>
      keyPrefix +<span style="color: rgba(0, 0, 0, 1)">
          (mappedChild.key </span>&amp;&amp; (!child || child.key !==<span style="color: rgba(0, 0, 0, 1)"> mappedChild.key)
            </span>? escapeUserProvidedKey(mappedChild.key) + '/'<span style="color: rgba(0, 0, 0, 1)">
            : </span>'') +<span style="color: rgba(0, 0, 0, 1)">
          childKey,
      );
    }
    result.push(mappedChild);
}
}</span></pre>
</div>
<p>这个方法里面做了什么呢,传入了三个参数,第一个参数就是&nbsp;bookKeeping,&nbsp;也就是&nbsp;traverseContext,也就是&nbsp;pool里面的东西。第二个参数&nbsp;child&nbsp;就是&nbsp;traverseAllChildrenImpl&nbsp;里面的直到单个的&nbsp;children,&nbsp;childKey&nbsp;就是一个映射关系,这个可以忽略。</p>
</div>
<p>&nbsp;</p>
<div>然后里面调用了&nbsp;bookKeeping&nbsp;里面的&nbsp;func&nbsp;,就是&nbsp;traverseContext&nbsp;里面的&nbsp;func,就是那个回调的函数。然后把节点传入,再传入一个&nbsp;index,&nbsp;调用了之后返回给&nbsp;mappedChild,然后判断是否是一个数组,如果是一个数组,会再次调用&nbsp;mapIntoWithKeyPrefixInternal&nbsp;,这就是一个大的递归,这时候递归的时候就不调用&nbsp;func&nbsp;,而是自己的&nbsp;c=&gt;c,直接返回这个节点,不然就无限循环了,对于&nbsp;map&nbsp;过的&nbsp;chilren&nbsp;,&nbsp;直接返回当前节点就可以了。最终,会判断下&nbsp;mappedChild&nbsp;是否是合理的&nbsp;element,&nbsp;也就是&nbsp;isValidElement&nbsp;,判断之后会判断&nbsp;cloneAndReplaceKey,</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre>export <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> cloneAndReplaceKey(oldElement, newKey) {
const newElement </span>=<span style="color: rgba(0, 0, 0, 1)"> ReactElement(
    oldElement.type,
    newKey,
    oldElement.ref,
    oldElement._self,
    oldElement._source,
    oldElement._owner,
    oldElement.props,
);


</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> newElement;
}</span></pre>
</div>
<p>这里&nbsp;cloneAndReplaceKey&nbsp;也非常简单,他其实就是&nbsp;return&nbsp;了一个新的&nbsp;react&nbsp;element。除了一个&nbsp;newKey&nbsp;之外,其他都是在原来的&nbsp;react&nbsp;element&nbsp;的基础上返回</p>
</div>
<p>&nbsp;</p>
<div>这就是&nbsp;map&nbsp;function&nbsp;两层嵌套递归的过程,把所有数组递归,然后把他展开返回的一个过程,所以这里&nbsp;getPooledTraverseContext&nbsp;里面的&nbsp;pool&nbsp;,有多少个数组,就有多少个对象,这就是&nbsp;pool&nbsp;设置在这里的含义,他不仅仅就是一层的情况下,永远都只有一个对象,如果数组里面是有嵌套的,而且嵌套是比较多的情况下,那么他的意义就比较大了。这就是&nbsp;React.Children.map的实现过程。&nbsp;forEachChildren&nbsp;跟&nbsp;mapChildren&nbsp;类似。下面还有几个,&nbsp;toArray</div>
<p>
<br><br></p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> toArray(children) {
const result </span>=<span style="color: rgba(0, 0, 0, 1)"> [];
mapIntoWithKeyPrefixInternal(children, result, </span><span style="color: rgba(0, 0, 255, 1)">null</span>, child =&gt;<span style="color: rgba(0, 0, 0, 1)"> child);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p>toArray就比较简单了,只是&nbsp;mapChildren&nbsp;里面的一层。</p>
</div>
<p>&nbsp;</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> onlyChild(children) {
invariant(
    isValidElement(children),
    </span>'React.Children.only expected to receive a single React element child.'<span style="color: rgba(0, 0, 0, 1)">,
);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> children;
}</span></pre>
</div>
<p>onlyChild&nbsp;其实就是判断是否是单个合理的&nbsp;react&nbsp;element节点&nbsp;。</p>
</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/wzndkj/p/11963333.html
頁: [1]
查看完整版本: React源码 React.Children