React源碼API-React.Children

React.Children 常用API源碼解讀

我們在寫React組件時(shí)候脖阵,經(jīng)常會有通過this.props.children傳遞給組件進(jìn)行渲染包裹頁面,通常會通過this.props.children.map()來遍歷渲染轧邪,其實(shí)React有提供一個(gè)API來做這里拖吼。也就是我們今天要說的React.Children.map

問題
  1. 直接數(shù)組的map和React.Children.map有什么區(qū)別
  2. React.Children.map和React.Children.forEach有什么區(qū)別

首先鼻疮,我們寫個(gè)例子

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來進(jìn)行處理

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

React的Children是個(gè)對象假勿,里面有map借嗽、forEach等等方法,map對應(yīng)的就是mapChildren函數(shù)转培,我們看看這個(gè)函數(shù)做了什么

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 - 我們傳遞進(jìn)來的ComWarp包裹的子內(nèi)容是個(gè)數(shù)組恶导,里面是每個(gè)div的對象($$type是react.element)
  2. 創(chuàng)建了一個(gè)空的數(shù)組result最后返回,我們繼續(xù)看看mapIntoArray函數(shù),這個(gè)函數(shù)比較長浸须,我們分幾個(gè)部分來說
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是一個(gè)數(shù)組,那也就是object類型删窒,走下switch object,發(fā)現(xiàn)沒有case符合裂垦,因?yàn)橹皇莻€(gè)純數(shù)組而已沒有$$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." );
        }
      }
    }
  }

這里其實(shí)就會判斷children是個(gè)數(shù)組的話(成立)肌索,就會遍歷children蕉拢,得到每個(gè)子級繼續(xù)進(jìn)行mapIntoArray(看到?jīng)]遞歸函數(shù))

nextName定義一個(gè)常量+本身的key,用于動態(tài)生成key給沒個(gè)元素的props添加后續(xù)方法cloneAndReplaceKey

i=0,child的子級就是具體某一個(gè)react元素,繼續(xù)執(zhí)行mapIntoArray,此時(shí)我們再返回到第一部分诚亚,子級就是一個(gè)object,且是個(gè)react元素$$typeof是REACT_ELEMENT_TYPE, invokeCallback設(shè)置true,我們繼續(xù)看true后會執(zhí)行什么

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

執(zhí)行了我們傳遞過來的callback函數(shù)也就是(c) => ()),執(zhí)行完后判斷返回的mappedChild的類型晕换,如果不是數(shù)組且不是null,就會把mappedChild放入到array(一開始我們定義的空數(shù)組)

如果mappedChild是數(shù)組,也就是我們傳遞的(c) =>[c,c],那就會繼續(xù)mapIntoArray站宗,此時(shí)mapIntoArray的chilren是返回的[c闸准,c]的數(shù)組,callback改造成c=>c份乒,而不是開始的(c) =>[c,c]

最后往復(fù)遞歸形成新的一個(gè)數(shù)組result

流程圖

image.png

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

調(diào)用的也是mapChildren函數(shù)恕汇,區(qū)別于map是map會返回一個(gè)新的數(shù)組,而forEach只是處理forEachFunc不返回


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

調(diào)用mapChildren或辖,返回處理好的數(shù)組


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颂暇,一起剝皮案震驚了整個(gè)濱河市缺谴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耳鸯,老刑警劉巖湿蛔,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異县爬,居然都是意外死亡阳啥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門财喳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來察迟,“玉大人斩狱,你說我怎么就攤上這事≡浚” “怎么了所踊?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長概荷。 經(jīng)常有香客問我秕岛,道長,這世上最難降的妖魔是什么误证? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任继薛,我火速辦了婚禮,結(jié)果婚禮上雷厂,老公的妹妹穿的比我還像新娘惋增。我一直安慰自己,他們只是感情好改鲫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著林束,像睡著了一般像棘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壶冒,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天缕题,我揣著相機(jī)與錄音,去河邊找鬼胖腾。 笑死烟零,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咸作。 我是一名探鬼主播锨阿,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼记罚!你這毒婦竟也來了墅诡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤桐智,失蹤者是張志新(化名)和其女友劉穎末早,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體说庭,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡然磷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刊驴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姿搜。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痪欲,到底是詐尸還是另有隱情悦穿,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布业踢,位于F島的核電站栗柒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏知举。R本人自食惡果不足惜瞬沦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雇锡。 院中可真熱鬧逛钻,春花似錦、人聲如沸锰提。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽立肘。三九已至边坤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谅年,已是汗流浹背茧痒。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留融蹂,地道東北人旺订。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像超燃,于是被迫代替她去往敵國和親区拳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353