react.createElement方法探究

前言:今天偶有閑暇霸褒,突然想知道 虛擬DOM 是如何實現(xiàn)的膛檀,說到 虛擬DOM 馅扣,便不得不說說 createElement 方法斟赚。所以先看 createElement 方法,并于此記錄一波差油。(下次在寫這種源碼指不定啥時候)

目前看的版本: 16.13.1

看源碼準備工作

第一步:clone項目

第二步拗军,搜索:createElement, 搜索結(jié)果中,唯一能和定義沾邊的就是這個 React.d.ts 文件了

搜索結(jié)果.png

第三步:根據(jù) React.d.ts蓄喇,找到當(dāng)前目錄的 src发侵,接著找 React.jsts語法我還沒學(xué)過,不過看到 .ts妆偏,我就去找根目錄下的同名文件刃鳄,一般都能找到)。 看到這熟悉的聲明語句楼眷,我就知道我找到了 铲汪。

const createElement = __DEV__ ? createElementWithValidation : createElementProd;

開始正式查看

createElement 方法在 開發(fā)模式中使用 createElementWithValidation熊尉,在生產(chǎn)模式中使用 createElementProd。(開發(fā)者模式和生產(chǎn)模式的區(qū)分變量 __DEV__掌腰,為啥叫這個狰住,一會我研究研究在發(fā)一篇文章,頭一次考慮這么細)

一般在開發(fā)者模式中齿梁,注釋和代碼縮進都會很不錯催植,所以看開發(fā)者模式的方法 createElementWithValidation

createElementWithValidation勺择, 顧名思義 創(chuàng)建_元素_經(jīng)過_校驗创南。具體怎么校驗和創(chuàng)建,咱們接著往下看省核。 (這個方法是相對引用的文件稿辙,怎么找我就不說了)

createElementWithValidation 方法第一句。玩呢气忠。邻储。上來就是一函數(shù)。旧噪。 先看名字 isValidElementType吨娜,盲猜:是_有效_元素_類型, 點擊去瞅瞅淘钟。

isValidElementType.png

得嘞宦赠,看完第一行的這個函數(shù),咱們接著往下走米母。
為了方便勾扭,我直接在代碼注釋內(nèi)寫理解了

  // We warn in this case but don't throw. We expect the element creation to
  // succeed and there will likely be errors in render.
  /***
    在這種情況下我們會警告但不要拋出錯誤。我們希望元素創(chuàng)建成功铁瞒,并且呈現(xiàn)中可能會有錯誤尺借。
  **/
  if (!validType) {  /*** 如果沒有經(jīng)過前面的類型校驗 **/
    let info = '';
 /*** 如果是個 undefined 元素,或者 是個空對象精拟, 我們就提示他:

您可能忘記從定義組件的文件中導(dǎo)出組件,或者您可能混淆了默認導(dǎo)入和命名導(dǎo)入虱歪。(不得不說這個提示是真的友好)
 **/
    if (
      type === undefined ||
      (typeof type === 'object' &&
        type !== null &&
        Object.keys(type).length === 0)
    ) {
      info +=
        ' You likely forgot to export your component from the file ' +
        "it's defined in, or you might have mixed up default and named imports.";
    }

    /*** 獲取出錯文件路徑 或者出錯組件名稱蜂绎,方便查找, 查找路徑的這個方法后邊再看笋鄙,跟我目標虛擬DOM 不是很沾邊 **/
    const sourceInfo = getSourceInfoErrorAddendumForProps(props);
    if (sourceInfo) {
      /*** 如果存在引用信息师枣,就提示出錯路徑和出錯行數(shù) **/
      info += sourceInfo;
    } else {
      /*** 沒有引用,那就提示出錯組件名稱 **/
      info += getDeclarationErrorAddendum();
    }
    

  /*** 判斷組件 類型萧落,分為 null ,array, 自定義組件, 原生組件 **/
    let typeString;
    if (type === null) {
      typeString = 'null';
    } else if (Array.isArray(type)) {
      typeString = 'array';
    } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
      typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
      info =
        ' Did you accidentally export a JSX literal instead of a component?';
    } else {
      typeString = typeof type;
    }

    /*** 如果是開發(fā)模式践美,就拼接錯誤洗贰,并輸出 **/
    if (__DEV__) {
      console.error(
        'React.createElement: type is invalid -- expected a string (for ' +
          'built-in components) or a class/function (for composite ' +
          'components) but got: %s.%s',
        typeString,
        info,
      );
    }
  }

OK,第一大段落陨倡,錯誤驗證就算結(jié)束了敛滋。接下來請看第二段,生成 element 元素

  /*** 創(chuàng)建元素兴革,一個大函數(shù)绎晃,容后再解釋 **/
  const element = createElement.apply(this, arguments);

  // The result can be nullish if a mock or a custom function is used.
 /*** 如果使用mock或自定義函數(shù),則結(jié)果可能為空杂曲。 **/
  // TODO: Drop this when these are no longer allowed as the type argument.
/*** 當(dāng)不再允許這些參數(shù)作為類型參數(shù)時庶艾,刪除此項。 **/
  if (element == null) {
/*** 如果是 mock 或 自定義函數(shù)時擎勘, 返回 null元素 **/
    return element;
  }

  // Skip key warning if the type isn't valid since our key validation logic
  // doesn't expect a non-string/function type and can throw confusing errors.
  // We don't want exception behavior to differ between dev and prod.
  // (Rendering will throw with a helpful message and as soon as the type is
  // fixed, the key warnings will appear.)
/***翻譯
如果類型無效咱揍,則跳過密鑰警告,因為我們的密鑰驗證邏輯不需要非字符串/函數(shù)類型棚饵,并且可能會引發(fā)混淆錯誤煤裙。
我們不希望生產(chǎn)模式和開發(fā)模式的異常處理有所不同。
(提示一條有幫助的消息蟹地,在類型被修復(fù)之前积暖,將顯示關(guān)鍵警告。)
 **/
  if (validType) {
    for (let i = 2; i < arguments.length; i++) {
      /*** 函數(shù)大意:
確保每個元素要么在靜態(tài)位置傳遞怪与,要么在定義了顯式鍵屬性的數(shù)組中傳遞夺刑,要么在具有有效鍵屬性的對象文本中傳遞。 **/
      validateChildKeys(arguments[i], type);
    }
  }

/*** 校驗Props 有效性分别, 分為 fragment標簽和 普通標簽 **/
  if (type === REACT_FRAGMENT_TYPE) {
    validateFragmentProps(element);
  } else {
    validatePropTypes(element);
  }

/*** 元素創(chuàng)建完畢遍愿,返回元素 **/
  return element;

接下來,看重點耘斩。 createElement 方法 所在文件packages/react/src/ReactElement.js

首先沼填,函數(shù)部分上來就是這些聲明,這些聲明也標志了一個 react組件 需要的參數(shù)

  /***  props 名稱 **/
let propName;

/*** 提取props 括授,放到 props變量內(nèi) **/
  // Reserved names are extracted
  const props = {};

  let key = null;  /*** 元素 key 值 **/
  let ref = null; /*** 元素ref **/
  let self = null;  /*** 元素上一級 **/
  let source = null; /*** 元素路徑 **/

校驗 ref

/*** 存在 ref 坞笙,并且有效  ,函數(shù)內(nèi) `Object.getOwnPropertyDescriptor(config, 'ref').get;`寫的賊棒 **/
if (hasValidRef(config)) {
      ref = config.ref;

      if (__DEV__) {
        /*** 開發(fā)模式中荚虚,如果ref 無效薛夜,提示的內(nèi)容,應(yīng)該是新版才會用的 useRef 和 createRef **/
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }

校驗 key

 if (hasValidKey(config)) {
      key = '' + config.key;
    }

selfsource 進行校驗版述,賦值

self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;

props 屬性處理梯澜,利用遍歷和賦值

// Remaining properties are added to a new props object
    for (propName in config) {
    /*** hasOwnProperty = Object.prototype.hasOwnProperty;  可遍歷屬性
   RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};   沒有這些屬性
**/
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }

遷移 children

/*** 翻譯
子項可以是多個參數(shù),并且這些子項被轉(zhuǎn)移到新分配的props對象上渴析。
 **/
// Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

生成并返回 react元素

return ReactElement(element.type, key, ref, self, source, owner, props);

接下來是講解 ReactElement 方法晚伙。 packages/react/src/ReactElement.js

找到 ReactElement 方法之后吮龄,首先看到的就是這一大串注釋
我感覺挺有用,先翻譯一波咆疗。

/***
 * 方法介紹翻譯:
     一個用于創(chuàng)造 react元素的 格式化(???這么說應(yīng)該沒啥毛病)方法漓帚。
     這里不再遵循class聲明,請不要使用 new 來調(diào)用它民傻。
     另外胰默,檢查某個元素是否為react元素也不可以使用 instanceof 。
      可以使用 $$typeof 是否為  Symbol.for('react.element')漓踢。
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self 當(dāng)調(diào)用React.createElement時牵署,一個*臨時*助手,用于檢測“this”與“owner”是否不同喧半,以便我們發(fā)出警告奴迅。我們希望去掉owner并用箭頭函數(shù)替換字符串ref,只要this和owner相同挺据,行為就不會改變取具。
 * @param {*} source 表示文件名、行號或其它信息的注釋對象(由transpiler或其它方式添加)扁耐。
 * @internal
 **/

接下來看正式的代碼暇检,整個代碼只有一個聲明,一個返回婉称,所以只看聲明就OK了块仆。

const element = {
    /*** 翻譯: 這個標簽允許我們將其唯一地標識為一個React元素 **/
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
     /*** REACT_ELEMENT_TYPE = Symbol.for('react.element') **/

    /*** 翻譯: 屬于元素的內(nèi)置屬性 **/
    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    /*** 翻譯: 記錄創(chuàng)建此元素的組件。  **/
    // Record the component responsible for creating this element.
    _owner: owner,
  };
/*** 中間有一堆開發(fā)模式才有代碼王暗,這個先無視悔据,下面再講 **/
return element;

看到這里,有點輕微懵逼俗壹,竟然不是直接返回元素科汗,返回的竟然是個對象,沒有任何dom節(jié)點返回绷雏,設(shè)想:這里返回的對象头滔,在經(jīng)過框架的某個函數(shù),將變成 真實的dom涎显。

稍微提一下屬性修飾詞: MDN

  1. 形參1:obj : 要定義屬性的對象拙毫。
  2. 形參2:prop :要定義或修改的屬性的名稱或 Symbol
  3. 形參3:desc:要定義或修改的屬性描述符。
    | desc描述 | 描述 | 默認值|
    | ----------- | ----------- | -----------|
    | configurable | 該屬性的描述符才能夠被改變棺禾,同時該屬性是否可以被刪除 | false |
    | enumerable | 是否可枚舉,遍歷的時候是否顯示這個屬性 | false |
    | value | 該屬性的值 | undefined |
    | writable | value值是否可以被改變 | false|

看一下開發(fā)模式才有的這些屬性修飾詞吧

if (__DEV__) {
    /*** 翻譯:
驗證標志現(xiàn)在是可變的峭跳。我們把它放在一個外部后備存儲器上膘婶,這樣我們就可以凍結(jié)整個對象缺前。一旦它們在常用的開發(fā)環(huán)境中執(zhí)行(難道是在說變化?)悬襟,就可以用WeakMap來代替它們衅码。
 **/
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    /*** 翻譯: 
為了使比較ReactElements更容易用于測試,我們將驗證標志設(shè)為不可枚舉(在可能的情況下脊岳,它應(yīng)該包括我們運行測試的每個環(huán)境)逝段,因此測試框架忽略它。
**/
    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    /*** _self屬性:不可枚舉割捅,不可刪除奶躯,不可賦值 **/
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });

    /*** 翻譯:
在兩個不同的地方創(chuàng)建的兩個元素在測試時應(yīng)該被認為是相等的,因此我們將其隱藏在枚舉中亿驾。 **/
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    /*** 如果可以嘹黔,凍結(jié) 元素和 元素的 props 屬性 **/
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市莫瞬,隨后出現(xiàn)的幾起案子儡蔓,更是在濱河造成了極大的恐慌,老刑警劉巖疼邀,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喂江,死亡現(xiàn)場離奇詭異,居然都是意外死亡旁振,警方通過查閱死者的電腦和手機获询,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來规求,“玉大人筐付,你說我怎么就攤上這事∽柚祝” “怎么了瓦戚?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丛塌。 經(jīng)常有香客問我较解,道長,這世上最難降的妖魔是什么赴邻? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任印衔,我火速辦了婚禮,結(jié)果婚禮上姥敛,老公的妹妹穿的比我還像新娘奸焙。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布与帆。 她就那樣靜靜地躺著了赌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玄糟。 梳的紋絲不亂的頭發(fā)上勿她,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音阵翎,去河邊找鬼逢并。 笑死,一個胖子當(dāng)著我的面吹牛郭卫,可吹牛的內(nèi)容都是我干的砍聊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼箱沦,長吁一口氣:“原來是場噩夢啊……” “哼辩恼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谓形,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤灶伊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寒跳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聘萨,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年童太,在試婚紗的時候發(fā)現(xiàn)自己被綠了米辐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡书释,死狀恐怖翘贮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爆惧,我是刑警寧澤狸页,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站扯再,受9級特大地震影響芍耘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熄阻,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一斋竞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秃殉,春花似錦坝初、人聲如沸浸剩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乒省。三九已至,卻和暖如春畦木,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砸泛。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工十籍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唇礁。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓勾栗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盏筐。 傳聞我的和親對象是個殘疾皇子围俘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360