React源碼學(xué)習(xí)——ReactElement.createElement

最近在學(xué)習(xí)React的源碼武花,從比較簡單的創(chuàng)建ReactElement開始學(xué)起袄琳,以下是今天要啃的源碼,可能有些地方還不是很深入稚配,相信隨著了解的增多,對react的理解也會更加深入:

ReactElement.createElement = function (type, config, children) {
  var propName;
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if ("development" !== 'production') {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if ("development" !== 'production') {
    if (key || ref) {
      if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) {
        var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
        if (key) {
          defineKeyPropWarningGetter(props, displayName);
        }
        if (ref) {
          defineRefPropWarningGetter(props, displayName);
        }
      }
    }
  }
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

How to trigger it ?

兩種方式可以觸發(fā)上面這個方法:

  1. 使用JSX創(chuàng)建元素
render() {
  return (
    <button className="button" type="button")}>Click</button>
  )
}
  1. 使用React.createElement創(chuàng)建元素
render() {
  var config = {
    className: 'button',
    type: 'button'
  };
  return React.createElement('button', config, 'Click');
}

JSX本質(zhì)上是ReactJS創(chuàng)建元素的語法糖港华,它們都在做同一件事情道川,就是生成一個簡單的按鈕,babel會幫我們將JSX轉(zhuǎn)化成Javascript立宜。

Parameters passed into the method

這個方法看起來是接收三個參數(shù)冒萄,不過了解Javascript函數(shù)機制的話,就知道其實不是的橙数,不過我們先假裝它接收三個參數(shù):

  1. type: 這個參數(shù)聲明要創(chuàng)建什么類型的DOM元素尊流,分兩種,一種是原生DOM灯帮,比如div span h1等等崖技,傳入一個string逻住,比如'div' 'span' 'h1'等等,即可正確的表示要創(chuàng)建的DOM類型迎献。另一種是自定義的Web Component瞎访,比如MyComponent,此時需要引入這個組件忿晕,然后將組件作為第一個參數(shù)傳入装诡。

The first part of a JSX tag determines the type of the React element.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX <Foo /> expression, Foo must be in scope.(出自ReactJS官方文檔)

  1. config: 用來聲明組件的屬性列表。
  2. children: 用來在組件內(nèi)創(chuàng)建子元素践盼,例子中是Click鸦采,我們也可以創(chuàng)建更復(fù)雜的子元素,比如div等等咕幻。

Then, what happened?

  1. 首先渔伯,在config不為空的情況下,校驗是否有合法的ref屬性以及是否是合法的鍵值對肄程。
function hasValidRef(config) {
  if ("development" !== 'production') {
    if (hasOwnProperty.call(config, 'ref')) {
      var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  return config.ref !== undefined;
}

本菜鳥一直搞不懂問什么要寫"development" !== 'production'這樣一句永遠為true的表達式(React源碼中經(jīng)常出現(xiàn))锣吼,歡迎大家評論解答_
首先判斷ref是否是config自身的屬性,而不是從原型鏈上繼承來的屬性蓝厌,然后再判斷此ref是否有效玄叠。

function hasValidKey(config) {
  if ("development" !== 'production') {
    if (hasOwnProperty.call(config, 'key')) {
      var getter = Object.getOwnPropertyDescriptor(config, 'key').get;
      if (getter && getter.isReactWarning) {
        return false;
      }
    }
  }
  return config.key !== undefined;
}

key的判斷跟ref很類似。

  1. 裝載self和source
    我們的例子中config__self __source均為undefined拓提,這兩個變量還沒有搞懂读恃,歡迎大家留言。

  2. config中的自有而非從原型鏈繼承得來代态、非保留屬性(key ref __self __source)存儲到局部變量props中寺惫。

  3. 前面提到過假裝只接受三個參數(shù),其實可以接受多于三個參數(shù)的蹦疑,那么第四個西雀、第五個參數(shù)要怎么處理呢?這個方法中歉摧,它們被當(dāng)做子元素來處理
    首先判斷子元素的長度是否為1艇肴,是的話直接將第三個參數(shù)放入props.children中,如果大于1叁温, 那么構(gòu)建子元素數(shù)組豆挽,將第三個、第四個券盅、第五個...參數(shù)依次放入這個數(shù)組中,然后將這個數(shù)組賦值給props.children膛檀。
    將參數(shù)指定的屬性設(shè)置好之后锰镀,開始處理指定元素類型帶有默認值的屬性娘侍,如果被設(shè)置默認值的屬性沒有被指定新值,那么存儲默認值到props中泳炉。

  4. 接下來的一段代碼憾筏,在props存在keyref的情況下,并且props.$$typeof為空或者不等于Symbol(react.element)或者60103時(即不是ReactElement)花鹅,為props.key props.ref添加get屬性氧腰,get屬性指向一個函數(shù),這個函數(shù)可以顯示初次的報警信息刨肃。這一步顯然與第1步有聯(lián)系古拴,但仍然想不出在什么情形下會顯示警告,大家知道的話可以告訴我~
    下面這段代碼是REACT_ELEMENT_TYPE的賦值操作

var REACT_ELEMENT_TYPE = typeof Symbol === 'function' && Symbol['for'] && Symbol['for']('react.element') || 0xeac7;

Symbol是ES6新定義的一種基本數(shù)據(jù)類型:
MDN web docs: Symbol

感覺defineXXXPropWarningGetter就是為key ref屬性添加訪問控制真友,不知道對不對...(原諒React小白留下這么多坑...)黄痪,以下是代碼:

function defineKeyPropWarningGetter(props, displayName) {
  var warnAboutAccessingKey = function () {
    if (!specialPropKeyWarningShown) {
      specialPropKeyWarningShown = true;
      "development" !== 'production' ? warning(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName) : void 0;
    }
  };
  warnAboutAccessingKey.isReactWarning = true;
  Object.defineProperty(props, 'key', {
    get: warnAboutAccessingKey,
    configurable: true
  });
}

function defineRefPropWarningGetter(props, displayName) {
  var warnAboutAccessingRef = function () {
    if (!specialPropRefWarningShown) {
      specialPropRefWarningShown = true;
      "development" !== 'production' ? warning(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName) : void 0;
    }
  };
  warnAboutAccessingRef.isReactWarning = true;
  Object.defineProperty(props, 'ref', {
    get: warnAboutAccessingRef,
    configurable: true
  });
}
  1. 萬事俱備,現(xiàn)在可以正兒八經(jīng)的創(chuàng)建ReactElement啦(構(gòu)建ReactElement數(shù)據(jù)結(jié)構(gòu))
    首先給這個element打上react的標簽(REACT_ELEMENT_TYPE)盔然,然后將校驗合格的變量填充到element對象中桅打,其中owner是ReactCurrentOwner.current,就是管理它的component愈案, 并且定義_store.validated _self _source(是否可配置意鲸,是否可編輯,是否可枚舉既荚,值)
var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner
  };

  if ("development" !== 'production') {
    element._store = {};
    if (canDefineProperty) {
      Object.defineProperty(element._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: false
      });
      Object.defineProperty(element, '_self', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
      });
      Object.defineProperty(element, '_source', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
      });
    } else {
      element._store.validated = false;
      element._self = self;
      element._source = source;
    }
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
  return element;
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遂鹊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子崇众,更是在濱河造成了極大的恐慌掂僵,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顷歌,死亡現(xiàn)場離奇詭異锰蓬,居然都是意外死亡,警方通過查閱死者的電腦和手機眯漩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門芹扭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赦抖,你說我怎么就攤上這事舱卡。” “怎么了队萤?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵轮锥,是天一觀的道長。 經(jīng)常有香客問我要尔,道長舍杜,這世上最難降的妖魔是什么新娜? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮既绩,結(jié)果婚禮上概龄,老公的妹妹穿的比我還像新娘。我一直安慰自己饲握,他們只是感情好私杜,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著救欧,像睡著了一般衰粹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颜矿,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天寄猩,我揣著相機與錄音,去河邊找鬼骑疆。 笑死田篇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的箍铭。 我是一名探鬼主播泊柬,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诈火!你這毒婦竟也來了兽赁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冷守,失蹤者是張志新(化名)和其女友劉穎刀崖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拍摇,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡亮钦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了充活。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜂莉。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖混卵,靈堂內(nèi)的尸體忽然破棺而出映穗,到底是詐尸還是另有隱情,我是刑警寧澤幕随,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布蚁滋,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枢赔。R本人自食惡果不足惜澄阳,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踏拜。 院中可真熱鬧,春花似錦低剔、人聲如沸速梗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姻锁。三九已至,卻和暖如春猜欺,著一層夾襖步出監(jiān)牢的瞬間位隶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工开皿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涧黄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓赋荆,卻偏偏與公主長得像笋妥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窄潭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 3. JSX JSX是對JavaScript語言的一個擴展語法春宣, 用于生產(chǎn)React“元素”,建議在描述UI的時候...
    pixels閱讀 2,828評論 0 24
  • 學(xué)習(xí)如何在Flow中使用React 將Flow類型添加到React組件后嫉你,F(xiàn)low將靜態(tài)地確保你按照組件被設(shè)計的方...
    vincent_z閱讀 6,354評論 4 21
  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時月帝,對React的特性、重點和注意事項的提取幽污、精練和總結(jié)嚷辅,可以做為React特性...
    科研者閱讀 8,234評論 2 21
  • 深入JSX date:20170412筆記原文其實JSX是React.createElement(componen...
    gaoer1938閱讀 8,070評論 2 35
  • 紀念每次無論何種理由的離開,夜未褪去油挥,黎明初醒潦蝇,在路上 放飛白色的鴿子于天際我所有的想象和思緒變成它們白色的羽翼 ...
    郭安安閱讀 337評論 10 17