React源碼解析之React.Children.map()(五)


 

一,React.Children是什么?

     是為了處理this.props.children(this.props.children表示所有組件的子節點)這個屬性提供的工具,是頂層的api之一

   React.children的用處:https://www.cnblogs.com/sunxiaopei/p/13805645.html

二,React.Children.map的結構及舉例

 結構:React.Children.map(object children,function fn [, object context])


 舉例如下:

 
    // this.props.children:數據類型為數組為ChildrenDemo的子節點(<span>1</span>和 <span>2</span>)   React.Children.map用來遍歷ChildrenDemo的子節點數目 1.<span>1</span> 2.<span>2</span>
class ChildrenDemo extends React.Component {
  render() {
    return (
      <ol>
        {
          React.Children.map(this.props.children, (child)=> {//callback child子節點
            return <li>{child}</li>
          })
        }
      </ol>
    )
  }
}
class MessageList extends React.Component {
  render() {
    return (
      <ChildrenDemo>
        <span>1</span>
        <span>2</span>
      </ChildrenDemo>
    )
  }
}  

this.props.children值有三種情況

1.如果組件沒有節點   值為undefined

2.如果組件有一個節點 值的數據類型為對象

3.如果組件有多個節點  值的數據類型為數組

React.Children.map(this.props.children, child => [child, [child]]):來遍歷this.props.children的子節點 多層嵌套的 [child, [child]])通過map之后平鋪成一維數組[child,child]

這里兩個子節點<span>1</span>和<span>2</span>,這里每一個節點都是數組 通過React.Children.map變成一個一維數組 

 

三、React.Children.map()中map方法的源碼解析
源碼中: Children{map: mapChildren}  //所以真正的名字是mapChildren
 mapChildren()
    //children:被遍歷的子組件
    //func:單個子組件需要執行的函數 
    //context:func執行時候,this指針指的對象
    function mapChildren(children, func, context) {
      if (children == null) {
        return children;
      }
      var result = [];
      //this.props.children   [] null child=>[child,[child]] undefined
      mapIntoWithKeyPrefixInternal(children, result, null, func, context);
      //執行完mapIntoWithKeyPrefixInternal方法后 返回result
      return result;
    }
 解析: 首先定義一個result,然后執行一個函數以后,返回這個result
 
mapIntoWithKeyPrefixInternal()
    /*
     第一次:children:[child1,child2]    array:[]  prefix:null  func:child=>[child,[child]]  context:undefined
     第二次:children:[child1,[child1]]  array:[]  prefix:".0"  func:c=>c                    context:undefined
     第三次:children:[child2,[child2]]  array:[]  prefix:".1"  func:c=>c                    context:undefined
    */
    function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
      var escapedPrefix = '';
      //處理key 如果字符串有多個/ 在匹配的字符串后面加一個/ 一般第二層遞歸用到 第一層prefix為null
      if (prefix != null) {
        escapedPrefix = escapeUserProvidedKey(prefix) + '/';
      }
      //從traverseContextPool里面里面獲取一個context
      var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);
      //多個節點 循環每一個節點 將嵌套的數組展平
 traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
      //context對象清空然后放回到traverseContextPool里面
 releaseTraverseContext(traverseContext);
    }

 解析:escapeUserProvidedKey()/getPooledTraverseContext()/traverseAllChildren()/releaseTraverseContext()函數的包裹器

escapeUserProvidedKey()

    function escapeUserProvidedKey(text) {
      //如果字符串中有多個/的話 在匹配的字符后加/
      //let a='aa/a/'  =>// aa/a//
      return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
    }
 解析: react對key定義的規則 如果字符串有多個/的話在字符串后面加/
 
getPooledTraverseContext()
 //創建一個對象池 復用對象 從而減少對象帶來的內存暫用 和性能消耗
    var POOL_SIZE = 10;    //對象池的最大容量
    var traverseContextPool = [];//全局變量 對象池 有多少個數組,就有多少個對象,這就是traverseContextPool設置在這里的含義
    function getPooledTraverseContext(mapResult, keyPrefix, mapFunction, mapContext) {
      //如果對象池內存在對象 則出隊一個對象
      if (traverseContextPool.length) {//如果全局變量有子節點
        var traverseContext = traverseContextPool.pop();//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
        };
      }
    }

 解析:創建一個對象池 復用對象 從而減少對象帶來的內存暫用 和性能消耗

  traverseAllChildren()
   //traverseAllChildrenImpl的觸發器
    function traverseAllChildren(children, callback, traverseContext) {
      if (children == null) {
        return 0;
      }

      return traverseAllChildrenImpl(children, '', callback, traverseContext);
    }

  解析:traverseAllChildrenImpl的觸發器

releaseTraverseContext()

 //將對象屬性清空並且重新放入對象池中
 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);
      }
    }

 解析:將對象屬性清空並且重新放入對象池中

traverseAllChildrenImpl()
    //核心遞歸函數 目的為展平數組
    //對於可以循環的children 都會重復調用traverseAllChildrenImpl 直到一個節點 然后調用callback
    function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
      var type = typeof children;

      if (type === 'undefined' || type === 'boolean') {
        //undefined null都被認為為null
        children = null;
      }
      //調用fun的flag
      var invokeCallback = false;

      if (children === null) {
        invokeCallback = true;
      } else {
        switch (type) {
          case 'string':
          case 'number':
            invokeCallback = true;
            break;
          //如果props.children單個節點
          //遞歸traverAllChildenTml時 <span>1</span>和<span>2</span>作為child
          //必會invokeCallback=true
          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.
          //如果只有一個節點 直接調用callback
          //<span>1</span> key=".0"
          nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
        return 1;
      }

      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];
          //不手動設置key的話 第一層第一個時。0 第二個。1
          nextName = nextNamePrefix + getComponentKey(child, i);
          //把child作為參數 進行遞歸 直到為單個節點的時候 去調用callback
          subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
        }
      } else {
        var iteratorFn = getIteratorFn(children);

        if (typeof iteratorFn === 'function') {
          {
            // Warn about using Maps as children
            if (iteratorFn === children.entries) {
              !didWarnAboutMaps ? warning$1(false, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.') : void 0;
              didWarnAboutMaps = true;
            }
          }

          var iterator = iteratorFn.call(children);
          var step;
          var 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') {
          //如果是純對象
          var addendum = '';

          {
            addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum();
          }

          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) + ")." + addendum);
            }
          }
        }
      }

      return subtreeCount;
    }

解析:當type的值為不可遍歷的undefined/boolean/string/number/object時候 則直接調用callback 回調函數

核心遞歸函數 目的為展平數組,對於可以循環的children 都會重復調用0 直到一個節點 然后調用callback也就是mapSingleChildIntoContext

mapSingleChildIntoContext()
 //復制除了key以外的屬性 替換key屬性 將其放到result中 bookKeeping:context對象
    function mapSingleChildIntoContext(bookKeeping, child, childKey) {
      var result = bookKeeping.result,
        keyPrefix = bookKeeping.keyPrefix,
        func = bookKeeping.func,
        context = bookKeeping.context;
      //調用fun回調函數
      var mappedChild = func.call(context, child, bookKeeping.count++);
      //對每一個節點 如果是數組 進行遞歸
      if (Array.isArray(mappedChild)) { //如果返回一個數組 這里返回的是[child,[child]]是一維數組
        //大遞歸 這次 不再調用fun 如果調用fun則無限循環 所以直接返回c
        mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, function (c) {
          return c;
        });
      } else if (mappedChild != null) {
        if (isValidElement(mappedChild)) {//isValidElement 判斷是否是合理的reactElement元素
          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);
        }
        //替換一下key推入到result中
        result.push(mappedChild);
      }
    }

解析: mapSingleChildIntoContext這個方法其實就是調用React.Children.map(children, callback)這里的callback,就是我們傳入的第二個參數,並得到map之后的結果。注意重點來了,如果map之后的節點還是一個數組,那么再次進入mapIntoWithKeyPrefixInternal,那么這個時候我們就會再次從pool里面去context了,而pool的意義大概也就是在這里了,如果循環嵌套多了,可以減少很多對象創建和gc的損耗,而如果不是數組並且是一個合規的ReactElement,就觸達頂點了,替換一下key就推入result

cloneAndReplaceKey()
  //返回一個新的reactElement 替換了newKey 其他的都是老得reactElement
    function cloneAndReplaceKey(oldElement, newKey) {
      var newElement = ReactElement(oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props);
      return newElement;
    }

解析:返回一個新的reactElement 替換了newKey 其他的都是老得reactElement

 

流程圖如下

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM