美文网首页
React源码API-React.Children

React源码API-React.Children

作者: suphu | 来源:发表于2020-12-24 21:58 被阅读0次

    React.Children 常用API源码解读

    我们在写React组件时候,经常会有通过this.props.children传递给组件进行渲染包裹页面,通常会通过this.props.children.map()来遍历渲染,其实React有提供一个API来做这里。也就是我们今天要说的React.Children.map

    问题
    1. 直接数组的map和React.Children.map有什么区别
    2. React.Children.map和React.Children.forEach有什么区别

    首先,我们写个例子

    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来进行处理

    var Children = {
      map: mapChildren,
      forEach: forEachChildren,
      count: countChildren,
      toArray: toArray,
      only: onlyChild
    };
    

    React的Children是个对象,里面有map、forEach等等方法,map对应的就是mapChildren函数,我们看看这个函数做了什么

    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;
    }
    
    1. children - 我们传递进来的ComWarp包裹的子内容是个数组,里面是每个div的对象($$type是react.element)
    2. 创建了一个空的数组result最后返回,我们继续看看mapIntoArray函数,这个函数比较长,我们分几个部分来说
    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是一个数组,那也就是object类型,走下switch object,发现没有case符合,因为只是个纯数组而已没有$$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." );
            }
          }
        }
      }
    

    这里其实就会判断children是个数组的话(成立),就会遍历children,得到每个子级继续进行mapIntoArray(看到没递归函数)

    nextName定义一个常量+本身的key,用于动态生成key给没个元素的props添加后续方法cloneAndReplaceKey

    i=0,child的子级就是具体某一个react元素,继续执行mapIntoArray,此时我们再返回到第一部分,子级就是一个object,且是个react元素$$typeof是REACT_ELEMENT_TYPE, invokeCallback设置true,我们继续看true后会执行什么

     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;
      }
    

    执行了我们传递过来的callback函数也就是(c) => ()),执行完后判断返回的mappedChild的类型,如果不是数组且不是null,就会把mappedChild放入到array(一开始我们定义的空数组)

    如果mappedChild是数组,也就是我们传递的(c) =>[c,c],那就会继续mapIntoArray,此时mapIntoArray的chilren是返回的[c,c]的数组,callback改造成c=>c,而不是开始的(c) =>[c,c]

    最后往复递归形成新的一个数组result

    流程图

    image.png
    React.Children.forEach
    function forEachChildren(children, forEachFunc, forEachContext) {
      mapChildren(children, function () {
        forEachFunc.apply(this, arguments); // Don't return anything.
      }, forEachContext);
    }
    
    

    调用的也是mapChildren函数,区别于map是map会返回一个新的数组,而forEach只是处理forEachFunc不返回


    toArray
    function toArray(children) {
      return mapChildren(children, function (child) {
        return child;
      }) || [];
    }
    

    调用mapChildren,返回处理好的数组


    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元素

    相关文章

      网友评论

          本文标题:React源码API-React.Children

          本文链接:https://www.haomeiwen.com/subject/qbnogktx.html