摘要:大家可以看到是構(gòu)造函數(shù)構(gòu)造出來的,并且內(nèi)部有一個對象,這個對象是本文接下來要重點(diǎn)介紹的對象,接下來我們就來一窺究竟吧。在構(gòu)造函數(shù)內(nèi)部就進(jìn)行了一步操作,那就是創(chuàng)建了一個對象,并掛載到了上。下一篇文章還是流程相關(guān)的內(nèi)容。
這是我的剖析 React 源碼的第二篇文章,如果你沒有閱讀過之前的文章,請務(wù)必先閱讀一下 第一篇文章 中提到的一些注意事項,能幫助你更好地閱讀源碼。
文章相關(guān)資料React 16.8.6 源碼中文注釋,這個鏈接是文章的核心,文中的具體代碼及代碼行數(shù)都是依托于這個倉庫
熱身篇
render 流程(二)
現(xiàn)在請大家打開 我的代碼 并定位到 react-dom 文件夾下的 src 中的 ReactDOM.js 文件,今天的內(nèi)容會從這里開始。
render想必大家在寫 React 項目的時候都寫過類似的代碼
ReactDOM.render(<APP />, document.getElementById("root")
這句代碼告訴了 React 應(yīng)用我們想在容器中渲染出一個組件,這通常也是一個 React 應(yīng)用的入口代碼,接下來我們就來梳理整個 render 的流程,并且會分為幾篇文章來講解,因為流程實(shí)在太長了。
首先請大家先定位到 ReactDOM.js 文件的第 702 行代碼,開始今天的旅程。
這部分代碼其實(shí)沒啥好說的,唯一需要注意的是在調(diào)用 legacyRenderSubtreeIntoContainer 函數(shù)時寫死了第四個參數(shù) forceHydrate 為 false。這個參數(shù)為 true 時表明了是服務(wù)端渲染,因為我們分析的是客戶端渲染,因此后面有關(guān)這部分的內(nèi)容也不會再展開。
接下來進(jìn)入 legacyRenderSubtreeIntoContainer 函數(shù)中,這部分代碼分為兩塊來講。第一部分是沒有 root 之前我們首先需要創(chuàng)建一個 root(對應(yīng)這篇文章),第二部分是有 root 之后的渲染流程(對應(yīng)接下來的文章)。
一開始進(jìn)來函數(shù)的時候肯定是沒有 root 的,因此我們需要去創(chuàng)建一個 root,大家可以發(fā)現(xiàn)這個 root 對象同樣也被掛載在了 container._reactRootContainer 上,也就是我們的 DOM 容器上。 如果你手邊有 React 項目的話,在控制臺鍵入如下代碼就可以看到這個 root 對象了。
document.querySelector("#root")._reactRootContainer
大家可以看到 root 是 ReactRoot 構(gòu)造函數(shù)構(gòu)造出來的,并且內(nèi)部有一個 _internalRoot 對象,這個對象是本文接下來要重點(diǎn)介紹的 fiber 對象,接下來我們就來一窺究竟吧。
首先還是和上文中提到的 forceHydrate 屬性相關(guān)的內(nèi)容,不需要管這部分,反正 shouldHydrate 肯定為 false。
接下來是將容器內(nèi)部的節(jié)點(diǎn)全部移除,一般來說我們都是這樣寫一個容器的的
<div id="root">div>
這樣的形式肯定就不需要去移除子節(jié)點(diǎn)了,這也側(cè)面說明了一點(diǎn)那就是容器內(nèi)部不要含有任何的子節(jié)點(diǎn)。一是肯定會被移除掉,二來還要進(jìn)行 DOM 操作,可能還會涉及到重繪回流等等。
最后就是創(chuàng)建了一個 ReactRoot 對象并返回。接下來的內(nèi)容中我們會看到好幾個 root,可能會有點(diǎn)繞。
在 ReactRoot 構(gòu)造函數(shù)內(nèi)部就進(jìn)行了一步操作,那就是創(chuàng)建了一個 FiberRoot 對象,并掛載到了 _internalRoot 上。和 DOM 樹一樣,fiber 也會構(gòu)建出一個樹結(jié)構(gòu)(每個 DOM 節(jié)點(diǎn)一定對應(yīng)著一個 fiber 對象),FiberRoot 就是整個 fiber 樹的根節(jié)點(diǎn),接下來的內(nèi)容里我們將學(xué)習(xí)到關(guān)于 fiber 相關(guān)的內(nèi)容。這里提及一點(diǎn),fiber 和 Fiber 是兩個不一樣的東西,前者代表著數(shù)據(jù)結(jié)構(gòu),后者代表著新的架構(gòu)。
在 createFiberRoot 函數(shù)內(nèi)部,分別創(chuàng)建了兩個 root,一個 root 叫做 FiberRoot,另一個 root 叫做 RootFiber,并且它們兩者還是相互引用的。
這兩個對象內(nèi)部擁有著數(shù)十個屬性,現(xiàn)在我們沒有必要一一去了解它們各自有什么用處,在當(dāng)下只需要了解少部分屬性即可,其他的屬性我們會在以后的文章中了解到它們的用處。
對于 FiberRoot 對象來說,我們現(xiàn)在只需要了解兩個屬性,分別是 containerInfo 及 current。前者代表著容器信息,也就是我們的 document.querySelector("#root");后者指向 RootFiber。
對于 RootFiber 對象來說,我們需要了解的屬性稍微多點(diǎn)
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 上文中已經(jīng)講過了,這里就不再贅述。
return、child、sibling 這三個屬性很重要,它們是構(gòu)成 fiber 樹的主體數(shù)據(jù)結(jié)構(gòu)。fiber 樹其實(shí)是一個單鏈表樹結(jié)構(gòu),return 及 child 分別對應(yīng)著樹的父子節(jié)點(diǎn),并且父節(jié)點(diǎn)只有一個 child 指向它的第一個子節(jié)點(diǎn),即便是父節(jié)點(diǎn)有好多個子節(jié)點(diǎn)。那么多個子節(jié)點(diǎn)如何連接起來呢?答案是 sibling,每個子節(jié)點(diǎn)都有一個 sibling 屬性指向著下一個子節(jié)點(diǎn),都有一個 return 屬性指向著父節(jié)點(diǎn)。這么說可能有點(diǎn)繞,我們通過圖來了解一下這個 fiber 樹的結(jié)構(gòu)。
const APP = () => (
<div>
<span>span>
<span>span>
div>
)
ReactDom.render(<APP/>, document.querySelector("#root"))
假如說我們需要渲染出以上組件,那么它們對應(yīng)的 fiber 樹應(yīng)該長這樣
從圖中我們可以看到,每個組件或者 DOM 節(jié)點(diǎn)都會對應(yīng)著一個 fiber 對象。另外你手邊有 React 項目的話,也可以在控制臺輸入如下代碼,查看 fiber 樹的整個結(jié)構(gòu)。
// 對應(yīng)著 FiberRoot
const fiber = document.querySelector("#root")._reactRootContainer._internalRoot
另外兩個屬性在本文中雖然用不上,但是看源碼的時候筆者覺得很有意思,就打算拿出來說一下。
在說 effectTag 之前,我們先來了解下啥是 effect,簡單來說就是 DOM 的一些操作,比如增刪改,那么 effectTag 就是來記錄所有的 effect 的,但是這個記錄是通過位運(yùn)算來實(shí)現(xiàn)的,這里 是 effectTag 相關(guān)的二進(jìn)制內(nèi)容。
如果我們想新增一個 effect 的話,可以這樣寫 effectTag |= Update;如果我們想刪除一個 effect 的話,可以這樣寫 effectTag &= ~Update。
最后是 alternate 屬性。其實(shí)在一個 React 應(yīng)用中,通常來說都有兩個 fiebr 樹,一個叫做 old tree,另一個叫做 workInProgress tree。前者對應(yīng)著已經(jīng)渲染好的 DOM 樹,后者是正在執(zhí)行更新中的 fiber tree,還能便于中斷后恢復(fù)。兩棵樹的節(jié)點(diǎn)互相引用,便于共享一些內(nèi)部的屬性,減少內(nèi)存的開銷。畢竟前文說過每個組件或 DOM 都會對應(yīng)著一個 fiber 對象,應(yīng)用很大的話組成的 fiber 樹也會很大,如果兩棵樹都是各自把一些相同的屬性創(chuàng)建一遍的話,會損失不少的內(nèi)存空間及性能。
當(dāng)更新結(jié)束以后,workInProgress tree 會將 old tree 替換掉,這種做法稱之為 double buffering,這也是性能優(yōu)化里的一種做法,有興趣的同學(xué)可以自行查找資料。
總結(jié)以上就是本文的全部內(nèi)容了,最后通過一張流程圖總結(jié)一下這篇文章的內(nèi)容。
最后
閱讀源碼是一個很枯燥的過程,但是收益也是巨大的。如果你在閱讀的過程中有任何的問題,都?xì)g迎你在評論區(qū)與我交流。
另外寫這系列是個很耗時的工程,需要維護(hù)代碼注釋,還得把文章寫得盡量讓讀者看懂,最后還得配上畫圖,如果你覺得文章看著還行,就請不要吝嗇你的點(diǎn)贊。
下一篇文章還是 render 流程相關(guān)的內(nèi)容。
最后,覺得內(nèi)容有幫助可以關(guān)注下我的公眾號 「前端真好玩」咯,會有很多好東西等著你。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/7216.html
摘要:就是,如果你不了解這個的話可以閱讀下相關(guān)文檔,是應(yīng)用初始化時就會生成的一個變量,值也是,并且這個值不會在后期再被改變。這是我的剖析 React 源碼的第三篇文章,如果你沒有閱讀過之前的文章,請務(wù)必先閱讀一下 第一篇文章 中提到的一些注意事項,能幫助你更好地閱讀源碼。 文章相關(guān)資料 React 16.8.6 源碼中文注釋,這個鏈接是文章的核心,文中的具體代碼及代碼行數(shù)都是依托于這個倉庫 熱身...
摘要:我們先來看下這個函數(shù)的一些神奇用法對于上述代碼,也就是函數(shù)來說返回值是。不管你第二個參數(shù)的函數(shù)返回值是幾維嵌套數(shù)組,函數(shù)都能幫你攤平到一維數(shù)組,并且每次遍歷后返回的數(shù)組中的元素個數(shù)代表了同一個節(jié)點(diǎn)需要復(fù)制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個系列文章: 現(xiàn)在工作中基本都用 React 了,由此想了解下內(nèi)部原理 市面上 Vue 的源碼解讀數(shù)不勝數(shù),但是反觀...
摘要:把組件看作狀態(tài)機(jī)有限狀態(tài)機(jī)使用來控制本地狀態(tài)使用來傳遞狀態(tài)前面我們探討了如何映射狀態(tài)到上初始渲染那么接下來我們談?wù)剷r如何同步狀態(tài)到上的也就是是如何更新組件的是如何對比出頁面變化最小的部分這篇文章會為你解答這些問題在這之前你已經(jīng)了解了版本內(nèi) React 把組件看作狀態(tài)機(jī)(有限狀態(tài)機(jī)), 使用state來控制本地狀態(tài), 使用props來傳遞狀態(tài). 前面我們探討了 React 如何映射狀態(tài)...
摘要:查看創(chuàng)建核心函數(shù)源碼行調(diào)用函數(shù)創(chuàng)建是相關(guān),不用管源碼行這個指的是調(diào)用創(chuàng)建,下面我們將會說到對象源碼行源碼行函數(shù)中,首先創(chuàng)建了一個,然后又創(chuàng)建了一個,它們兩者還是相互引用。 感謝 yck: 剖析 React 源碼解析,本篇文章是在讀完他的文章的基礎(chǔ)上,將他的文章進(jìn)行拆解和加工,加入我自己的一下理解和例子,便于大家理解。覺得yck寫的真的很棒 。React 版本為 16.8.6,關(guān)于源碼的...
摘要:為了能夠更好的使用這個工具,今天就對它進(jìn)行一下源碼剖析。它內(nèi)部的關(guān)鍵代碼是在不指定的時候等于,這就意味著的源碼剖析到此結(jié)束,謝謝觀看當(dāng)然如果指定了剖析就還得繼續(xù)。好了,源碼剖析到此結(jié)束,謝謝觀看 React-Redux是用在連接React和Redux上的。如果你想同時用這兩個框架,那么React-Redux基本就是必須的了。為了能夠更好的使用這個工具,今天就對它進(jìn)行一下源碼剖析。 Pr...
閱讀 1459·2021-11-22 13:52
閱讀 1281·2021-09-29 09:34
閱讀 2690·2021-09-09 11:40
閱讀 3031·2019-08-30 15:54
閱讀 1255·2019-08-30 15:53
閱讀 971·2019-08-30 11:01
閱讀 1354·2019-08-29 17:22
閱讀 1943·2019-08-26 10:57