從渲染原理到性能優化(一)

前言

如下,是我在 2018 React Conf 的分享內容,但願對你們有所幫助。能夠先在 官網下載個人ppt對照看,效果更佳哦~。

不少人都使用過React,可是不多人能說出它內部的渲染原理。有人會說,會用就好了,知道渲染原理有必要麼?其實渲染原理決定着性能優化的方法,只有在瞭解原理以後,才能徹底理解爲何這樣作能夠優化性能。正所謂:知其然,而後知其因此然。
廢話很少說,下面咱們就開始吧~ react

本篇文章,將會分爲四部分介紹:數組

JSX如何生成element
當咱們寫下一段JSX代碼的時候,react是如何根據咱們的JSX代碼來生成虛擬DOM的組成元素element的。瀏覽器

element如何生成真實DOM節點
再生成elment以後,react又如何將其轉成瀏覽器的真實節點。這裏會經過介紹首次渲染以及更新渲染的流程來幫助你們理解這個渲染流程。性能優化

性能優化
結合渲染原理,經過實際例子,看看如何優化組件。babel

React 16異步渲染方案
到目前爲止,這些優化組件的方法還不能解決什麼問題,因此咱們須要引入異步渲染,以及異步渲染的原理是什麼。app

1、JSX如何生成element

這裏是一段寫在render裏的jsx代碼。dom

return (
    <div className="cn">
         <Header> Hello, This is React </Header>
         <div>Start to learn right now!</div>
         Right Reserve.
    </div>
)

首先,它會通過babel編譯成React.createElement的表達式。異步

return (
    React.createElement(
        'div',
        { className: 'cn' },
        React.createElement(
            Header,
            null,
            'Hello, This is React'
        ),
        React.createElement(
            'div',
            null,
            'Start to learn right now!'
        ),
        'Right Reserve'
    )
)

這個createElement方法是作什麼的呢?函數

其實從它的名字就能夠看出,這是用來生成element的。element在React裏,其實就是組成虛擬DOM 樹的節點,它用來描述你想要在瀏覽器上看到什麼。
它的參數有三個:
一、type -> 標籤
二、attributes -> 標籤屬性,沒有的話,能夠爲null
三、children -> 標籤的子節點性能

這個React.createElement的表達式會在render函數被調用的時候執行,換句話說,當render函數被調用的時候,會返回一個element
說了那麼久element,這個element究竟長什麼樣呢?
其實,它就是一個對象,以下:

{
  type: 'div',
    props: {
      className: 'cn',
        children: [
          {
            type: function Header,
            props: {
                children: 'Hello, This is React'
            }
          },
          {
            type: 'div',
            props: {
                children: 'start to learn right now!'
            }
          },
          'Right Reserve'
      ]
  }
}

咱們來觀察一下這個對象的children,如今有三種類型:
一、string
二、原生DOM節點
三、React Component - 自定義組件

除了這三種,還有兩種類型:
四、fale ,null, undefined,number
五、數組 - 使用map方法的時候

這裏須要記住一個點:element不必定是Object類型。

2、element如何生成真實節點

順利獲得element以後,咱們再來看看React是如何把element轉化成真實DOM節點的。
首先,須要去初始化element,初始化的規則以下:
以第一條爲例:先判斷是否爲Object類型,是的話,看它的type是不是原生DOM標籤,是的話,給它建立ReactDOMComponent的實例對象,其餘同理。
圖片描述

這時候有的人可能會有所疑問:這些個ReactDOMComponent, ReactCompositeComponentWrapper怎麼開發的時候都沒有見過?

其實這些都是React的私有類,React本身使用,不會暴露給用戶的。它們的經常使用方法有:mountComponent,updateComponent等。其中mountComponent 用於建立組件,而updateComponent用於用戶更新組件。而咱們自定義組件的生命週期函數以及render函數都是在這些私有類的方法裏被調用的。
既然這些私有類的方法那麼重要咱們就先來簡單瞭解一下吧~

ReactDOMComponent

首先是ReactMComponent的mountComponent方法,這個方法的做用是:將element轉成真實DOM節點,而且插入到相應的container裏,而後返回markup(realDOM)。
由此可知ReactDOMComponent的mountComponent是element生成真實節點的關鍵
下面看個栗子它是怎麼作到的吧。
圖片描述

假設有這樣一個type類型是原生DOM的element:

{
  type: 'div',
    props: {
    className: 'cn',
      children: 'Hello world',
    }
}

簡單mountComponent的實現:

mountComponent(container) {
  const domElement = document.createElement(this._currentElement.type);
  const textNode = document.createTextNode(this._currentElement.props.children);

  domElement.appendChild(textNode);
  container.appendChild(domElement);
  return domElement;
}

其實實現的過程很簡單,就是根據type生成domElement,再將子節點append進來返回。固然,真實的mountComponent沒有那麼簡單,感興趣的能夠本身去看源碼啦。
這裏須要記住的一個點是:這個類的mountComponent方法會本身操做瀏覽器DOM元素
講完ReactDOMComponent,再來看看ReactCompositeComponentWrapper。

ReactCompositeComponentWrapper

這個類的mountComponent方法做用是:實例化自定義組件,最後是經過遞歸調用到ReactDOMComponent的mountComponent方法來獲得真實DOM。
注意:也就是說他本身是不直接生成DOM節點的。
那這個遞歸是一個怎樣的過程呢?咱們經過首次渲染來看下。

首次渲染

假設咱們有一個Example的組件,它返回<div>hello world</div> 這樣一個標籤。
首次渲染的過程以下:
圖片描述

首先從React.render開始,因爲咱們剛剛說,render函數被調用的時候會返回一個element,因此此時返回給咱們的element是:

{
  type: function Example,
  props: {
    children: null
  }
}

因爲這個type是一個自定義組件類,此時要初始化的類是ReactCompositeComponentWrapper,接着調用它的mountComponent方法。這裏面會作四件事情,詳情能夠看上圖。其中,第二步的render的獲得的element爲:

{
  type: 'div',
    props: {
    children: 'Hello World'
  }
}

因爲這個type是一個原生DOM標籤,此時要初始化的類是ReactDOMComponent。接下來它的mountComponent方法就能夠幫咱們生成對應的DOM節點放在瀏覽器裏啦。
這時候有人可能會有疑問,若是第二步render出來的element 類型也是自定義組件呢?
這時候它就會去調用ReactCompositeComponentWrapper的mountComponent方法,從而造成了一個遞歸。無論你的自定義組件嵌套多少層,最後總會生成原生dom類型的element,因此最後必定能調用到ReactDOMComponent的mountComponent方法。
感興趣的能夠本身在打斷點看下這個遞歸的過程。
由我打的斷點圖能夠看出在ReactCompositeComponent的mountComponent被調用屢次以後,最後調用到了ReactDOMComponent的mountComponent方法。
圖片描述

到這裏,首次渲染的過程就基本講完了:-D。
可是還有一個問題:前面咱們說自定義組件的生命週期跟render函數都是在私有類的方法裏被調用的,如今只看到render函數被調用了,那麼首次渲染時候生命週期函數 componentWillMount 跟 componentDidMount 在哪被調用呢?
圖片描述

由圖可知,在第一步獲得instance對象以後,就會去看instance.componentWillMount是否有被定義,有的話調用,而在整個渲染過程結束以後調用componentDidMount。
以上,就是渲染原理的部分,讓咱們來總結如下:

  • JSX代碼通過babel編譯以後變成React.createElement的表達式,這個表達式在render函數被調用的時候執行生成一個element。
  • 在首次渲染的時候,先去按照規則初始化element,接着ReactComponentComponentWrapper經過遞歸,最終調用ReactDOMComponent的mountComponent方法來幫助生成真實DOM節點。
相關文章
相關標籤/搜索