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

blank

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

前言

以下,是我在2018 React Conf的分享內容,希望對大家有所幫助。可以先在官網下載我的ppt對照看,效果更佳哦~☺。

很多人都使用過React,但是很少人能說出它內部的渲染原理。有人會說,會用就行了,知道渲染原理有必要么?其實渲染原理決定著性能優化的方法,只有在了解原理之後,才能完全理解為什麼這樣做可以優化性能。正所謂:知其然,然後知其所以然。

廢話不多說,下面我們就開始吧~

本篇文章,將會分為四部分介紹:

  • JSX如何生成element

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

  • element如何生成真實DOM節點

再生成elment之後,react又如何將其轉成瀏覽器的真實節點。這裡會通過介紹首次渲染以及更新渲染的流程來幫助大家理解這個渲染流程,

  • 性能優化

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

  • React 16異步渲染方案

到目前為止,這些優化組件的方法還不能解決什麼問題,所以我們需要引入異步渲染,以及異步渲染的原理是什麼。


一、JSX如何生成element

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

return(<divclassName="cn"><Header>Hello,ThisisReact</Header><div>Starttolearnrightnow!</div>RightReserve.</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樹的節點,它用來描述你想要在瀏覽器上看到什麼

它的參數有三個:

1、type -> 標籤

2、attributes -> 標籤屬性,沒有的話,可以為null

3、children -> 標籤的子節點

這個React.createElement的表達式會在render函數被調用的時候執行,換句話說,當render函數被調用的時候,會返回一個element

說了那麼久element,這個element究竟長什麼樣呢?

其實,它就是一個對象,如下:

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

我們來觀察一下這個對象的children,現在有三種類型:

1、string

2、原生DOM節點

3、React Component - 自定義組件

除了這三種,還有兩種類型:

4、false ,null, undefined,number

5、數組- 使用map方法的時候

這裡需要記住一個點: element不一定是Object類型。


二、element如何生成真實節點

順利得到element之後,我們再來看看React是如何把element轉化成真實DOM節點的。

首先,需要去初始化element,初始化的規則如下:

先判斷是否為Object類型,是的話,看它的type是否是原生DOM標籤,是的話,給它創建ReactDOMComponent的實例對象,其他同理。

blank

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

其實這些都是React的私有類,React自己使用,不會暴露給用戶的。它們的常用方法有:mountComponent,updateComponent等。其中mountComponent 用於創建組件,而updateComponent用於用戶更新組件。而我們自定義組件的生命週期函數以及render函數都是在這些私有類的方法裡被調用的。

既然這些私有類的方法那麼重要我們就先來簡單了解一下吧~

ReactDOMComponent

首先是ReactMComponent的mountComponent方法,這個方法的作用是:將element轉成真實DOM節點,並且插入到相應的container裡,然後返回markup(realDOM)。

由此可知ReactDOMComponent的mountComponent是element生成真實節點的關鍵

下面看個栗子它是怎麼做到的吧。

圖片來源:https://www.fabiaoqing.com/biaoqing/detail/id/53371.html

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

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

簡單mountComponent的實現:

mountComponent(container){constdomElement=document.createElement(this._currentElement.type);consttextNode=document.createTextNode(this._currentElement.props.children);domElement.appendChild(textNode);container.appendChild(domElement);returndomElement;}

其實實現的過程很簡單,就是根據type生成domElement,再將子節點append進來返回。當然,真實的mountComponent沒有那麼簡單,感興趣的可以自己去看源碼啦。

這裡需要記住的一個點是:這個類的mountComponent方法會自己操作瀏覽器DOM元素

講完ReactDOMComponent,再來看看ReactCompositeComponentWrapper。

ReactCompositeComponentWrapper

這個類的mountComponent方法作用是:實例化自定義組件,最後是通過遞歸調用到ReactDOMComponent的mountComponent方法來得到真實DOM。

注意:也就是說他自己是不直接生成DOM節點的

那這個遞歸是一個怎樣的過程呢?我們通過首次渲染來看下。


首次渲染

假設我們有一個Example的組件,它返回<div>hello world</div> 這樣一個標籤。

首次渲染的過程如下:

blank

首先從React.render開始,由於我們剛剛說,render函數被調用的時候會返回一個element,所以此時返回給我們的element是:

{type:functionExample,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方法。

blank

到這裡,首次渲染的過程就基本講完了:-D。

但是還有一個問題:前面我們說自定義組件的生命週期跟render函數都是在私有類的方法裡被調用的,現在只看到render函數被調用了,那麼首次渲染時候生命週期函數componentWillMount 跟componentDidMount在哪被調用呢?

blank

由圖可知,在第一步得到instance對象之後,就會去看instance.componentWillMount是否有被定義,有的話調用,而在整個渲染過程結束之後調用componentDidMount。

以上,就是渲染原理的部分,讓我們來總結以下:

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

What do you think?

Written by marketer

blank

React 官方團隊最近在幹嘛?

blank

使用go編寫webassembly