【譯】了解React源代碼-初始渲染(簡單組件)1

【譯】了解React源代碼-初始渲染(簡單組件)1
【譯】了解React源代碼-初始渲染(簡單組件)2
【譯】了解React源代碼-初始渲染(簡單組件)3
【譯】了解React源代碼-初始渲染(類組件)4
【譯】了解React源代碼-初始渲染(類組件)5
【譯】了解React源代碼-UI更新(事務)6
【譯】了解React源代碼-UI更新(事務)7
【譯】了解React源代碼-UI更新(單個DOM)8
【譯】了解React源代碼-UI更新(DOM樹)9


UI更新本質上就是數(shù)據(jù)更改。 React提供了一種簡單直觀的前端編程方法,所有活動部分都以狀態(tài)(state)的形式融合在一起。 我喜歡從數(shù)據(jù)結構入手,先對功能和處理邏輯有一個大致的了解,這也使代碼檢查變得更容易。 時不時,我對React在內部如何工作感到好奇,因此寫這篇文章。

It never hurts to have a deeper understanding down the stack, as it gives me more freedom when I need a new feature, more confidence when I want to contribute and more comfort when I upgrade.
深入了解堆棧永遠不會有傷害,因為當我需要一個新功能時,它為我提供了更多的自由,當我想做出貢獻時,它給了我更多的信心,而當我升級時,它給了我更多的舒適感。

本文將通過渲染一個簡單的組件(<h1>)來理解React。 其他主題(例如,復合組件渲染,狀態(tài)驅動的UI更新和組件生命周期)將在后續(xù)文章中以類似的方式進行討論。

本文中使用的文件:

isomorphic/React.js
ReactElement.createElement() 入口

isomorphic/classic/element/ReactElement.js
ReactElement.createElement() 實現(xiàn)

renderers/dom/ReactDOM.js
ReactDOM.render() 入口

renderers/dom/client/ReactMount.js
ReactDom.render() 實現(xiàn)

renderers/shared/stack/reconciler/instantiateReactComponent.js
根據(jù)元素類型創(chuàng)建不同類型的ReactComponents

renderers/shared/stack/reconciler/ReactCompositeComponent.js
ReactComponents根元素的包裝器

調用棧中使用的標簽:
- 函數(shù)調用
= 別名
~ 間接函數(shù)調用

由于不能從 flat module tree 中的import語句派生源代碼文件的位置,因此我將使用@來幫助在代碼片段清單中找到它們。

最后一點,本系列基于 React 15.6.2 。

從JSX到React.createElement()

I was not consciously aware of using React.createElement(), as the existence of it is masked by JSX from a developer’s point of view.
我沒有意識到要使用React.createElement(),因為從開發(fā)人員的角度來看,它的存在已被JSX掩蓋了。

在編譯時,Babel將JSX中定義的組件轉換為使用適當參數(shù)調用的React.createElement()。 例如,create-react-app 附帶的默認App.js:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

編譯為:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return React.createElement(
      'div',
      { className: 'App' },
      React.createElement(
        'header',
        { className: 'App-header' },
        React.createElement('img', { src: logo, className: 'App-logo', alt: 'logo' }),
        React.createElement(
          'h1',
          { className: 'App-title' },
          'Welcome to React'
        )
      ),
      React.createElement(
        'p',
        { className: 'App-intro' },
        'To get started, edit ',
        React.createElement(
          'code',
          null,
          'src/App.js'
        ),
        ' and save to reload.'
      )
    );
  }
}

export default App;

這是瀏覽器執(zhí)行的真實代碼。 上面的代碼顯示了復合組件App的定義,其中JSX,JavaScript代碼中的HTML標簽穿插語法(例如:<div className=”App”></div>),被轉換為React.createElement()調用。
在應用程序級別,將渲染此組件:

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

通常由一個名為“ index.js”的JS入口模塊組成。

這個嵌套的組件樹太復雜了,無法成為理想的起點,因此我們現(xiàn)在就忘記了它,而是看一些更簡單的東西-渲染一個簡單的HTML元素。

ReactDOM.render(
  <h1 style={{"color":"blue"}}>hello world</h1>,
  document.getElementById('root')
);

上面代碼的轉義版本是:

ReactDOM.render(React.createElement(
  'h1',
  { style: { "color": "blue" } },
  'hello world'
), document.getElementById('root'));

React.createElement() - 創(chuàng)建一個 ReactElement

第一步并沒有真正做很多事情。 它只是構造了一個ReactElement實例,其中填充了傳遞給調用堆棧的所有內容。 結果數(shù)據(jù)結構為:


element數(shù)據(jù)結構.png

調用棧為:

React.createElement
|=ReactElement.createElement(type, config, children)
  |-ReactElement(type,..., props)

React.createElement(type, config, children)僅僅是ReactElement.createElement()的別名;

...
var createElement = ReactElement.createElement;
...
var React = {
...
  createElement: createElement,
...
};

module.exports = React;

React@isomorphic/React.js

ReactElement.createElement(type,config,children)
1)將config中的元素復制到props;
2)將children復制到props.children;
3)將type.defaultProps復制到props;

...
  // 1)
  if (config != null) {
  ...extracting not interesting properties from config...
    // 其余屬性添加到新的props對象
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 2)
  // Children可以是多個參數(shù),并且這些參數(shù)將轉移到新分配的props對象上。
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children; // scr: 將一個children存儲為對象
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2]; // scr: 將多個children存儲為數(shù)組
    }

    props.children = childArray;
  }

  // 3)
  // 處理默認屬性
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
...

ReactElement.createElement@isomorphic/classic/element/ReactElement.js

然后ReactElement(type,...,props)typeprops照原樣復制到ReactElement并返回實例。

...
var ReactElement = function(type, key, ref, self, source, owner, props) {
    // 這個標簽可以讓我們唯一地將其識別為React元素
    $$typeof: REACT_ELEMENT_TYPE,

    // 元素的內置屬性
    type: // scr:------------------> 'h1'
    key:  // scr:------------------> 目前不感興趣
    ref:  // scr:------------------> 目前不感興趣
    props: {
        children:       // scr:------------------> 'hello world'
        ...other props: // scr:------------------> style: { "color": "blue" }
    },

    // 記錄負責創(chuàng)建此元素的組件。
    _owner: owner, // scr: --------------> null
};
...

ReactElement@isomorphic/classic/element/ReactElement.js

新構建的ReactElement中填充的字段將由ReactMount.instantiateReactComponent()直接使用,稍后將對其進行詳細說明。 請注意,下一步還將使用ReactElement.createElement()創(chuàng)建一個ReactElement對象,因此我將調用此階段的ReactElement對象ReactElement [1]。

ReactDom.render() - 渲染

_renderSubtreeIntoContainer()
TopLevelWrapper附加到ReactElement [1]

下一步的目的是將ReactElement [1]與另一個ReactElement(我們將實例稱為[2])包裝在一起,并使用TopLevelWrapper設置ReactElement.type。 名稱TopLevelWrapper解釋了它的作用-包裝通過render()傳遞的組件樹的頂級元素:

將1和2包裝在一起.png

這里的一個重要定義是·TopLevelWrapper·的定義,我在這里為CTL-f輸入三顆星***,因為您稍后可能需要回到該定義。

...
var TopLevelWrapper = function() {
  this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
TopLevelWrapper.prototype.render = function() {
  return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
...

TopLevelWrapper@renderers/dom/client/ReactMount.js

請注意,分配給ReactElement.type的實體是一種類型(TopLevelWrapper),它將在以下呈現(xiàn)步驟中實例化。 (然后將通過render()this.props.child中提取ReactElement [1]。)

構造指定對象的調用棧如下:

ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer(
  parentComponent, // scr:-----------------> null
  nextElement,     // scr:-----------------> ReactElement[1]
  container,       // scr:-----------------> document.getElementById('root')
  callback'        // scr:-----------------> undefined
)

對于初始渲染,ReactMount._renderSubtreeIntoContainer()比看起來要簡單,實際上,該函數(shù)中的大多數(shù)分支(用于UI更新)都被跳過了。 在邏輯進行下一步之前唯一有效的行是:

...
    var nextWrappedElement = React.createElement(TopLevelWrapper, {
      child: nextElement,
    });
...

_renderSubtreeIntoContainer@renderers/dom/client/ReactMount.js

現(xiàn)在,應該很容易看到如何使用React.createElement構造此步驟的目標對象,React.createElement是我們在上一節(jié)中檢查過的函數(shù)。

InstantiateReactComponent()
使用ReactElement [2]創(chuàng)建一個ReactCompositeComponent

此步驟的目的是為頂層組件創(chuàng)建一個基本的ReactCompositeComponent

為頂層組件創(chuàng)建復合組件.png

此步驟的調用棧為:

ReactDOM.render
|=ReactMount.render(nextElement, container, callback)
|=ReactMount._renderSubtreeIntoContainer()
  |-ReactMount._renderNewRootComponent(
      nextWrappedElement, // scr:------------------> ReactElement[2]
      container,          // scr:------------------> document.getElementById('root')
      shouldReuseMarkup,  // scr: null from ReactDom.render()
      nextContext,        // scr: emptyObject from ReactDom.render()
    )
    |-instantiateReactComponent(
        node,             // scr:------------------> ReactElement[2]
        shouldHaveDebugID /* false */
      )
      |-ReactCompositeComponentWrapper(
          element         // scr:------------------> ReactElement[2]
        );
      |=ReactCompositeComponent.construct(element)

InstantiateReactComponent是唯一值得在這里討論的長函數(shù)。 在我們的上下文中,它將檢查ReactElement [2] .type(即TopLevelWrapper)并相應地創(chuàng)建一個ReactCompositeComponent

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;

...
  } else if (typeof node === 'object') {
    var element = node;
    var type = element.type;
...

    // Special case string values
    if (typeof element.type === 'string') {
...
    } else if (isInternalComponentType(element.type)) {
...
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
...
  } else {
...
  }

...
  return instance;
}

instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.js

值得注意的是,新的ReactCompositeComponentWrapper()ReactCompositeComponent構造函數(shù)的直接調用:

...
// To avoid a cyclic dependency, we create the final class in this module
var ReactCompositeComponentWrapper = function(element) {
  this.construct(element);
};
...

...
Object.assign(
  ReactCompositeComponentWrapper.prototype,
  ReactCompositeComponent,
  {
    _instantiateReactComponent: instantiateReactComponent,
  },
);
...

ReactCompositeComponentWrapper@renderers/shared/stack/reconciler/instantiateReactComponent.js

然后真正的構造函數(shù)被調用:

construct: function(element) {
    this._currentElement = element; // scr:------------> ReactElement[2]
    this._rootNodeID = 0;
    this._compositeType = null;
    this._instance = null;
    this._hostParent = null;
    this._hostContainerInfo = null;

    // See ReactUpdateQueue
    this._updateBatchNumber = null;
    this._pendingElement = null;
    this._pendingStateQueue = null;
    this._pendingReplaceState = false;
    this._pendingForceUpdate = false;

    this._renderedNodeType = null;
    this._renderedComponent = null;
    this._context = null;
    this._mountOrder = 0;
    this._topLevelWrapper = null;

    // See ReactUpdates and ReactUpdateQueue.
    this._pendingCallbacks = null;

    // ComponentWillUnmount shall only be called once
    this._calledComponentWillUnmount = false;
  },

ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

我們將在此步驟中創(chuàng)建的對象命名為ReactCompositeComponent [T](頂部為T)。

構造ReactCompositeComponent對象后,下一步是調用batchedMountComponentIntoNode,初始化ReactCompositeComponent [T]并安裝它,這將在下一篇文章中詳細討論。

(原文)Understanding The React Source Code - Initial Rendering (Simple Component) I

(下一篇)【譯】了解React源代碼-初始渲染(簡單組件)2

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容