美文网首页
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