這是個人剖析 React 源碼的第二篇文章,若是你沒有閱讀過以前的文章,請務必先閱讀一下 第一篇文章 中提到的一些注意事項,能幫助你更好地閱讀源碼。html
如今請你們打開 個人代碼 並定位到 react-dom 文件夾下的 src 中的 ReactDOM.js 文件,今天的內容會從這裏開始。前端
想必你們在寫 React 項目的時候都寫過相似的代碼react
ReactDOM.render(<APP />, document.getElementById('root') 複製代碼
這句代碼告訴了 React 應用咱們想在容器中渲染出一個組件,這一般也是一個 React 應用的入口代碼,接下來咱們就來梳理整個 render
的流程,而且會分爲幾篇文章來說解,由於流程實在太長了。git
首先請你們先定位到 ReactDOM.js 文件的第 702 行代碼,開始今天的旅程。github
這部分代碼其實沒啥好說的,惟一須要注意的是在調用 legacyRenderSubtreeIntoContainer
函數時寫死了第四個參數 forceHydrate
爲 false
。這個參數爲 true
時代表了是服務端渲染,由於咱們分析的是客戶端渲染,所以後面有關這部分的內容也不會再展開。性能優化
接下來進入 legacyRenderSubtreeIntoContainer
函數中,這部分代碼分爲兩塊來說。第一部分是沒有 root
以前咱們首先須要建立一個 root
(對應這篇文章),第二部分是有 root
以後的渲染流程(對應接下來的文章)。數據結構
一開始進來函數的時候確定是沒有 root
的,所以咱們須要去建立一個 root
,你們能夠發現這個 root
對象一樣也被掛載在了 container._reactRootContainer
上,也就是咱們的 DOM 容器上。 若是你手邊有 React 項目的話,在控制檯鍵入以下代碼就能夠看到這個 root
對象了。架構
document.querySelector('#root')._reactRootContainer
複製代碼
你們能夠看到 root
是 ReactRoot
構造函數構造出來的,而且內部有一個 _internalRoot
對象,這個對象是本文接下來要重點介紹的 fiber
對象,接下來咱們就來一窺究竟吧。dom
首先仍是和上文中提到的 forceHydrate
屬性相關的內容,不須要管這部分,反正 shouldHydrate
確定爲 false
。ide
接下來是將容器內部的節點所有移除,通常來講咱們都是這樣寫一個容器的的
<div id='root'></div>
複製代碼
這樣的形式確定就不須要去移除子節點了,這也側面說明了一點那就是容器內部不要含有任何的子節點。一是確定會被移除掉,二來還要進行 DOM 操做,可能還會涉及到重繪迴流等等。
最後就是建立了一個 ReactRoot
對象並返回。接下來的內容中咱們會看到好幾個 root
,可能會有點繞。
在 ReactRoot
構造函數內部就進行了一步操做,那就是建立了一個 FiberRoot
對象,並掛載到了 _internalRoot
上。和 DOM 樹同樣,fiber
也會構建出一個樹結構(每一個 DOM 節點必定對應着一個 fiber
對象),FiberRoot
就是整個 fiber
樹的根節點,接下來的內容裏咱們將學習到關於 fiber
相關的內容。這裏說起一點,fiber
和 Fiber 是兩個不同的東西,前者表明着數據結構,後者表明着新的架構。
在 createFiberRoot
函數內部,分別建立了兩個 root
,一個 root
叫作 FiberRoot
,另外一個 root
叫作 RootFiber
,而且它們二者仍是相互引用的。
這兩個對象內部擁有着數十個屬性,如今咱們沒有必要一一去了解它們各自有什麼用處,在當下只須要了解少部分屬性便可,其餘的屬性咱們會在之後的文章中瞭解到它們的用處。
對於 FiberRoot
對象來講,咱們如今只須要了解兩個屬性,分別是 containerInfo
及 current
。前者表明着容器信息,也就是咱們的 document.querySelector('#root')
;後者指向 RootFiber
。
對於 RootFiber
對象來講,咱們須要瞭解的屬性稍微多點
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) {
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.effectTag = NoEffect;
this.alternate = null;
}
複製代碼
stateNode
上文中已經講過了,這裏就再也不贅述。
return
、child
、sibling
這三個屬性很重要,它們是構成 fiber
樹的主體數據結構。fiber
樹實際上是一個單鏈表樹結構,return
及 child
分別對應着樹的父子節點,而且父節點只有一個 child
指向它的第一個子節點,即使是父節點有好多個子節點。那麼多個子節點如何鏈接起來呢?答案是 sibling
,每一個子節點都有一個 sibling
屬性指向着下一個子節點,都有一個 return
屬性指向着父節點。這麼說可能有點繞,咱們經過圖來了解一下這個 fiber
樹的結構。
const APP = () => (
<div> <span></span> <span></span> </div>
)
ReactDom.render(<APP/>, document.querySelector('#root'))
複製代碼
假如說咱們須要渲染出以上組件,那麼它們對應的 fiber
樹應該長這樣
從圖中咱們能夠看到,每一個組件或者 DOM 節點都會對應着一個 fiber
對象。另外你手邊有 React 項目的話,也能夠在控制檯輸入以下代碼,查看 fiber
樹的整個結構。
// 對應着 FiberRoot
const fiber = document.querySelector('#root')._reactRootContainer._internalRoot
複製代碼
另外兩個屬性在本文中雖然用不上,可是看源碼的時候筆者以爲頗有意思,就打算拿出來講一下。
在說 effectTag
以前,咱們先來了解下啥是 effect
,簡單來講就是 DOM 的一些操做,好比增刪改,那麼 effectTag
就是來記錄全部的 effect 的,可是這個記錄是經過位運算來實現的,這裏 是 effectTag
相關的二進制內容。
若是咱們想新增一個 effect
的話,能夠這樣寫 effectTag |= Update
;若是咱們想刪除一個 effect
的話,能夠這樣寫 effectTag &= ~Update
。
最後是 alternate
屬性。其實在一個 React 應用中,一般來講都有兩個 fiebr
樹,一個叫作 old tree,另外一個叫作 workInProgress tree。前者對應着已經渲染好的 DOM 樹,後者是正在執行更新中的 fiber tree,還能便於中斷後恢復。兩棵樹的節點互相引用,便於共享一些內部的屬性,減小內存的開銷。畢竟前文說過每一個組件或 DOM 都會對應着一個 fiber
對象,應用很大的話組成的 fiber
樹也會很大,若是兩棵樹都是各自把一些相同的屬性建立一遍的話,會損失很多的內存空間及性能。
當更新結束之後,workInProgress tree 會將 old tree 替換掉,這種作法稱之爲 double buffering,這也是性能優化裏的一種作法,有興趣的同窗能夠自行查找資料。
以上就是本文的所有內容了,最後經過一張流程圖總結一下這篇文章的內容。
閱讀源碼是一個很枯燥的過程,可是收益也是巨大的。若是你在閱讀的過程當中有任何的問題,都歡迎你在評論區與我交流。
另外寫這系列是個很耗時的工程,須要維護代碼註釋,還得把文章寫得儘可能讓讀者看懂,最後還得配上畫圖,若是你以爲文章看着還行,就請不要吝嗇你的點贊。
下一篇文章仍是 render 流程相關的內容。
最後,以爲內容有幫助能夠關注下個人公衆號 「前端真好玩」咯,會有不少好東西等着你。