美文网首页
packages/react/ReactChildren解析

packages/react/ReactChildren解析

作者: Ahungrynoob | 来源:发表于2019-08-04 10:55 被阅读0次

    ReactChildren

    React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。

    React.Children.map

    React.Children.map(children, function[(thisArg)])
    

    children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。

    注意

    如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。

    React.Children.forEach

    React.Children.forEach(children, function[(thisArg)])
    

    React.Children.map() 类似,但它不会返回一个数组。

    React.Children.count

    React.Children.count(children)
    

    返回 children 中的组件总数量,等同于通过 mapforEach 调用回调函数的次数。

    React.Children.only

    React.Children.only(children)
    

    验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。

    注意:

    React.Children.only() 不接受 React.Children.map() 的返回值,因为它是一个数组而并不是 React 元素。

    React.Children.toArray

    React.Children.toArray(children)
    

    children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递 this.props.children 之前对内容重新排序或获取子集时。

    注意:

    React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说,toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。


    源码解析

    some utils:

    const SEPARATOR = '.';
    const SUBSEPARATOR = ':';
    
    /**
     * Escape and wrap key so it is safe to use as a reactid
     *
     * @param {string} key to be escaped.
     * @return {string} the escaped key.
     */
    function escape(key) {
      const escapeRegex = /[=:]/g;
      const escaperLookup = {
        '=': '=0',
        ':': '=2',
      };
      const escapedString = ('' + key).replace(escapeRegex, function(match) {
        return escaperLookup[match];
      });
    
      return '$' + escapedString;
    }
    
    /**
     * TODO: Test that a single child and an array with one item have the same key
     * pattern.
     */
    
    let didWarnAboutMaps = false;
    
    const userProvidedKeyEscapeRegex = /\/+/g;
    function escapeUserProvidedKey(text) {
      return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
    }
    
    const POOL_SIZE = 10;
    const traverseContextPool = [];
    function getPooledTraverseContext(
      mapResult,
      keyPrefix,
      mapFunction,
      mapContext,
    ) {
      if (traverseContextPool.length) {
        const traverseContext = traverseContextPool.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,
        };
      }
    }
    
    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);
      }
    }
    
    • SEPARATOR:react key 的分隔符
    • SUBSEPARATOR: react sub-children key 的分隔符
    • escapeescapeUserProvidedKey: 转译key
    • getPooledTraverseContext: 如果traverseContextPool中有元素,就从traverseContextPool中拿出一个元素,这样做的好处是可以减少内存的占用。
      否则就创建一个新的元素,来存储context的信息:result,keyPrefix,func,context,count;
    • releaseTraverseContext:释放并初始化某个context,如果traverseContextPool的大小<10的话,这个context会被push进去。

    关键API和函数:

    traverseAllChildrenImpl()

    /**
     * @param {?*} children Children tree container.
     * @param {!string} nameSoFar Name of the key path so far.
     * @param {!function} callback Callback to invoke with each child found.
     * @param {?*} traverseContext Used to pass information throughout the traversal
     * process.
     * @return {!number} The number of children in this subtree.
     */
    function traverseAllChildrenImpl(
      children,
      nameSoFar,
      callback,
      traverseContext,
    ) {
      const type = typeof children;
    
      if (type === 'undefined' || type === 'boolean') {
        // All of the above are perceived as null.
        children = null;
      }
    
      let 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;
            }
        }
      }
    
      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.
          nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
        );
        return 1;
      }
    
      let child;
      let nextName;
      let subtreeCount = 0; // Count of children found in the current subtree.
      const nextNamePrefix =
        nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
    
      if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
          child = children[i];
          nextName = nextNamePrefix + getComponentKey(child, i);
          subtreeCount += traverseAllChildrenImpl(
            child,
            nextName,
            callback,
            traverseContext,
          );
        }
      } else {
        const iteratorFn = getIteratorFn(children);
        if (typeof iteratorFn === 'function') {
          if (__DEV__) {
            // Warn about using Maps as children
            if (iteratorFn === children.entries) {
              warning(
                didWarnAboutMaps,
                'Using Maps as children is unsupported and will likely yield ' +
                  'unexpected results. Convert it to a sequence/iterable of keyed ' +
                  'ReactElements instead.',
              );
              didWarnAboutMaps = true;
            }
          }
    
          const iterator = iteratorFn.call(children);
          let step;
          let 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') {
          let addendum = '';
          if (__DEV__) {
            addendum =
              ' If you meant to render a collection of children, use an array ' +
              'instead.' +
              ReactDebugCurrentFrame.getStackAddendum();
          }
          const childrenString = '' + children;
          invariant(
            false,
            'Objects are not valid as a React child (found: %s).%s',
            childrenString === '[object Object]'
              ? 'object with keys {' + Object.keys(children).join(', ') + '}'
              : childrenString,
            addendum,
          );
        }
      }
    
      return subtreeCount;
    }
    

    traverseAllChildrenImpl函数中,首先对children的类型做了判断。

    • 如果不是数组、map、普通对象类型,就直接执行传入的callback函数,并返回1。其中callback函数执行的时候传入的key是通过getComponentKey并转译得到的。
    • 如果children是数组对象,就递归调用自己,且在递归调用中,分隔符变成了SUBSEPARATOR;
    • 如果是Map对象,React会抛出警告日志,因为Map对象的迭代器是不保证顺序的。
    • 如果是普通的Object对象,就会抛出错误,并不会执行下去。

    forEach

    /**
     * Traverses children that are typically specified as `props.children`, but
     * might also be specified through attributes:
     *
     * - `traverseAllChildren(this.props.children, ...)`
     * - `traverseAllChildren(this.props.leftPanelChildren, ...)`
     *
     * The `traverseContext` is an optional argument that is passed through the
     * entire traversal. It can be used to store accumulations or anything else that
     * the callback might find relevant.
     *
     * @param {?*} children Children tree object.
     * @param {!function} callback To invoke upon traversing each child.
     * @param {?*} traverseContext Context for traversal.
     * @return {!number} The number of children in this subtree.
     */
    function traverseAllChildren(children, callback, traverseContext) {
      if (children == null) {
        return 0;
      }
    
      return traverseAllChildrenImpl(children, '', callback, traverseContext);
    }
    
    function forEachSingleChild(bookKeeping, child, name) {
      const {func, context} = bookKeeping;
      func.call(context, child, bookKeeping.count++);
    }
    
    /**
     * Iterates through children that are typically specified as `props.children`.
     *
     * See https://reactjs.org/docs/react-api.html#reactchildrenforeach
     *
     * The provided forEachFunc(child, index) will be called for each
     * leaf child.
     *
     * @param {?*} children Children tree container.
     * @param {function(*, int)} forEachFunc
     * @param {*} forEachContext Context for forEachContext.
     */
    function forEachChildren(children, forEachFunc, forEachContext) {
      if (children == null) {
        return children;
      }
      const traverseContext = getPooledTraverseContext(
        null,
        null,
        forEachFunc,
        forEachContext,
      );
      traverseAllChildren(children, forEachSingleChild, traverseContext);
      releaseTraverseContext(traverseContext);
    }
    

    forEachChildren即是forEachAPI,在这个函数中,会拿到一个初始化后的context,并在这个context中绑定开发者自定义的forEachFuncforEachContext,之后会执行traverseAllChildrentraverseAllChildren执行完毕后,会释放context

    map

    function mapSingleChildIntoContext(bookKeeping, child, childKey) {
      const {result, keyPrefix, func, context} = bookKeeping;
    
      let mappedChild = func.call(context, child, bookKeeping.count++);
      if (Array.isArray(mappedChild)) {
        mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => 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
            keyPrefix +
              (mappedChild.key && (!child || child.key !== mappedChild.key)
                ? escapeUserProvidedKey(mappedChild.key) + '/'
                : '') +
              childKey,
          );
        }
        result.push(mappedChild);
      }
    }
    
    function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
      let escapedPrefix = '';
      if (prefix != null) {
        escapedPrefix = escapeUserProvidedKey(prefix) + '/';
      }
      const traverseContext = getPooledTraverseContext(
        array,
        escapedPrefix,
        func,
        context,
      );
      traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
      releaseTraverseContext(traverseContext);
    }
    
    /**
     * Maps children that are typically specified as `props.children`.
     *
     * See https://reactjs.org/docs/react-api.html#reactchildrenmap
     *
     * The provided mapFunction(child, key, index) will be called for each
     * leaf child.
     *
     * @param {?*} children Children tree container.
     * @param {function(*, int)} func The map function.
     * @param {*} context Context for mapFunction.
     * @return {object} Object containing the ordered map of results.
     */
    function mapChildren(children, func, context) {
      if (children == null) {
        return children;
      }
      const result = [];
      mapIntoWithKeyPrefixInternal(children, result, null, func, context);
      return result;
    }
    

    mapChildren即是mapAPI,它会调用mapIntoWithKeyPrefixInternal,并把返回的结果存入result并返回。与forEach不同的是,mapChildren会为每一个component创建一个新的key。

    相关文章

      网友评论

          本文标题:packages/react/ReactChildren解析

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