React 源碼深度解讀(四):首次自定義組件渲染 - Part 1

  • 前言

React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript

本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java

  • 正文

前面三篇文章介紹了 React 是怎麼渲染普通DOM元素的,以下圖所示。node

clipboard.png

紅線部分生成的markup其實是一層一層往回傳,爲了方便展現就直接跳過中間層級返回了。這張圖片跳過了事務(transaction)相關的調用,後面會有專門的文章介紹。算法

本篇開始介紹自定義組件是如何渲染的。segmentfault

  • App 組件

將自定義組件命名爲App,結構以下:服務器

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      desc: 'start',
    };
  }

  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src="main.jpg" className="App-logo" alt="logo" />
          <h1> "Welcom to React" </h1>
        </div>
        <p className="App-intro">
          { this.state.desc }
        </p>
      </div>
    );
  }
}

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

App 通過 Babel 編譯後,生成以下代碼:app

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      desc: 'start',
    };
  }

  render() {
    return React.createElement(
      'div',
      { className: 'App' },
      React.createElement(
        'div',
        { className: 'App-header' },
        React.createElement(
          'img',
          { src: "main.jpg", className: 'App-logo', alt: 'logo' }
        ),
        React.createElement(
          'h1',
          null,
          ' "Welcom to React" '
        )
      ),
      React.createElement(
        'p',
        { className: 'App-intro' },
        this.state.desc
      )
    );
  }
}

ReactDOM.render(
  React.createElement(App, null),
  document.getElementById(‘root’)
);
  • 構建頂層包裝組件ReactCompositeComponent[T]

  1. 跟普通DOM元素渲染同樣,第一步先會執行React.createElement建立 type 爲 App 的 ReactElement[1]
  2. 而後在 _renderSubtreeIntoContainer 裏面建立 type 爲 TopLevelWrapper 的 ReactElement[2]
  3. 經過instantiateReactComponent建立包裝元素 ReactCompositeComponent[T]

clipboard.png

調用關係以下圖所示:學習

clipboard.png

  • 初始化ReactCompositeComponent[T]

  1. 在下一步mountComponentIntoNode時,ReactDOMContainerInfo[ins]會被建立並傳給ReactReconciler
  2. ReactReconciler會調用ReactCompositeComponent[T]的 mountComponent 建立 TopLevelWrapper 實例。
  3. 而後就是 performInitialMount 根據 ReactElement 的類型來建立不一樣的對象。在渲染普通 DOM 元素的時候,這部會返回 ReactDOMComponent。但渲染自定義組件的時候,就不同了。

clipboard.png

渲染普通 DOM 元素的調用關係以下圖所示,自定義組件的渲染調用關係見下文:this

clipboard.png

  • 使用 App 建立ReactCompositeComponent[ins]

在 performInitialMount 這步,renderedElement 就是 ReactElement[1]spa

performInitialMount: function (renderedElement, hostParent,
    hostContainerInfo, transaction, context) {
    ...
    
    // 這裏會調用 TopLevelWrapper 實例的 render 方法,獲得 ReactElement[1]
    if (renderedElement === undefined) {
        renderedElement = this._renderValidatedComponent();
    }

    ...
    
    // 返回 ReactCompositeComponent[ins]
    var child = this._instantiateReactComponent(
        renderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
    );
    
    this._renderedComponent = child;

    var markup = ReactReconciler.mountComponent(
        child,
        transaction,
        hostParent,
        hostContainerInfo,
        this._processChildContext(context),
        debugID
    );

    return markup;
},

這步與第二篇的 performInitialMount 很類似,惟一區別就是渲染普通 DOM 元素返回的是ReactDOMComponent,而渲染自定義組件返回的是包裝好的自定義組件ReactCompositeComponent[ins]

clipboard.png

調用關係以下圖所示:

clipboard.png

  • 初始化ReactCompositeComponent[ins]

在 performInitialMount 的後半部分,ReactReconciler.mountComponent 實際上會調用 ReactCompositeComponent[ins] 的 mountComponent。這裏的關鍵代碼是

...

// 建立 App 組件的實例
var inst = this._constructComponent(
    doConstruct,
    publicProps,
    publicContext,
    updateQueue
);

...

通過這步後,ReactCompositeComponent[ins]._instance 等於 App[ins]。像以前同樣,mountComponent 又會調用自身的 performInitialMount:

performInitialMount: function (renderedElement, hostParent,
    hostContainerInfo, transaction, context) {
    ...
    
    // 這裏會調用 App 實例的 render 方法,而 render 的返回值是 React.createElement 的嵌套調用。
    if (renderedElement === undefined) {
        renderedElement = this._renderValidatedComponent();
    }

    ...
    
    // 返回 ReactDOMComponent[6]
    var child = this._instantiateReactComponent(
        renderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
    );
    
    this._renderedComponent = child;

    var markup = ReactReconciler.mountComponent(
        child,
        transaction,
        hostParent,
        hostContainerInfo,
        this._processChildContext(context),
        debugID
    );

    return markup;
},

React.createElement 的嵌套調用是指:

render() {
    return React.createElement(           // scr: -----------> 5)
      'div',
      { className: 'App' },
      React.createElement(                // scr: -----------> 3)
        'div',
        { className: 'App-header' },
        React.createElement(              // scr: -----------> 1)
          'img',
          { src: "main.jpg", className: 'App-logo', alt: 'logo' }
        ),
        React.createElement(              // scr: -----------> 2)
          'h1',
          null,
          ' "Welcom to React" '
        )
      ),
      React.createElement(                // scr: -----------> 4)
        'p',
        { className: 'App-intro' },
        this.state.desc
      )
    );
  }

這裏 React.createElement 的調用順序是先調用做爲參數的 children,再調用父級。調用順序已在代碼中註釋。

clipboard.png

接下來的 _instantiateReactComponent 會返回ReactDOMComponent,就觸及到真正的 DOM 操做了。先看圖,這部份內容將在下回分解~

clipboard.png

相關文章
相關標籤/搜索