React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-建立更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...react
老的react架構從setState到render完成,整個過程是主要霸佔主線程的。若是組件比較大,或者有些複雜的邏輯,長時間佔用主線程,會致使一些input框輸入操做、動畫等得不到響應,從而表現出頁面卡頓。算法
爲了解決上述的問題,React引入了一個全新的異步渲染架構:Fiber。segmentfault
這是React 核心算法的一次大的更新,重寫了 React 的 reconciliation 算法。reconciliation 算法是用來更新而且渲染DOM樹的算法。之前React 15.x的版本使用的算法稱爲「stack reconciliation」,如今稱爲「fiber reconciler」。api
fiber reconciler主要的特色是能夠把更新流程拆分紅一個一個的小的單元進行更新,而且能夠中斷,轉而去執行高優先級的任務或者瀏覽器的動畫渲染等,等主線程空閒了再繼續執行更新。數組
另外的新功能:
一、render方法能夠返回多元素(便可以返回數組)
二、支持異常邊界處理異常;瀏覽器
爲了達到上述的效果,react將底層更新單元的數據結構改爲了鏈表結構。之前的協調算法是遞歸調用,經過react dom 樹級關係構成的棧遞歸。而fiber是扁平化的鏈表的數據存儲結構,經過child找子節點,return找父節點,sibling找兄弟節點。遍歷從遞歸改成循環。數據結構
具體的結構參照我下面畫的圖:
架構
建立上面的fiber樹對應的代碼:dom
import React from 'react'; import ReactDOM from 'react-dom' class List extends React.Component { render () { return ( [1,2,3].map((item)=>{ return <span>span</span> }) ) } } class App extends React.Component { render () { return ( [<button>按鈕</button>,<List/>,<div>div</div>] ); } } ReactDOM.render( <App />, document.getElementById("root") )
第一次渲染的時候會構建好這顆fiber樹。如下是構建這顆fiber樹的過程。
建立過程和更新過程實際上是一個過程,能夠說建立過程是更新過程的一個子集,至關於每一個節點的更新都是新建一個fiber節點。
其中粉色節點表明更新完成的節點,當全部的節點都變成粉色說明整棵fiber樹都已經準備好了。能夠提交到真實dom樹上去了。異步
建立一個RootFiber節點
建立RootFiber節點過程的詳細源碼解析歡迎閱讀:
React16源碼解析(二)-建立更新
構建/更新fiber樹過程詳細源碼解析歡迎閱讀:
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
沿着子節點不斷的建立fiber子節點,若是發現子節點是一個數組,會把子節點都建立好,以後拿到第一個子節點再往下走。
這裏圖中第一個子節點button它已經沒有子節點了,這個時候就會把這個節點是否有更新計算出來,算好更新以後就往回走了。我就稱這個節點構建完成了。
注:橘色節點只是建立好了fiber尚未完成。
由於3號節點(button)沒有子節點了,因此咱們向它的兄弟節點出發了。到達4號節點,又會以一樣的方式遍歷子節點。
當4號節點的子節點都完成以後,回到4號節點,再完成4號節點,由於4號節點存在兄弟節點,因此再向兄弟節點出發。
到達5號節點以後,5號節點再以一樣的方式遍歷子節點。
9號節點完成以後,就會一層層返回到root節點。由於返回的路上已經沒有兄弟節點了。直到root節點完成,這顆fiber樹就已經渲染好了,接下來就能夠提交渲染樹到真實的dom樹了。
假設我如今想要更新7號節點。
以下代碼:
import React from 'react'; import ReactDOM from 'react-dom' class List extends React.Component { render () { const { list } = this.props; return ( list.map((item)=>{ return <span>{item}</span> }) ) } } class App extends React.Component { constructor() { super(); this.state = { list:[1,2,3] } } clickButton = () => { this.setState({ list:[1,4,3] }) } render () { return ( [<button onClick={this.clickButton}>按鈕</button>,<List list={this.state.list}/>,<div>div</div>] ); } } ReactDOM.render( <App />, document.getElementById("root") )
點擊按鈕就會更新7號節點的內容,將 2 -> 4。
當遍歷到7號節點時候,發現7號節點是須要更新的,由於它身上有個叫effectTag的標誌,值爲4表示的是要更新本節點。這個節點須要更新因此把7號節點記錄在父節點的firstEffect鏈表上。如圖所示:
當遍歷到4號節點的時候,由於它身上firstEffect不爲空,因此它會把他身上的firstEffect接到父節點的身上。如圖所示:
遍歷到2號節點時,一樣的道理:
其實這裏firstEffect鏈表後面連接的7號是一直指向7號節點的指針。在提交階段(提交到dom樹上)直接遍歷root節點上的firstEffect鏈表就能夠了。由於這上面記錄了那些節點有更新,只須要更新咱們標記好的節點就能夠啦。
通過上述過程,可能你們會產生疑問,說好的可中斷呢?怎麼一個字也沒提呢???
別急,我如今一句話就能講清了:
上面個人圖中,個人每個步驟(實際狀況步驟更多,我沒畫那麼細)是能夠不連續佔用主線程的。
react把更新這顆fiber樹切分紅了好多個任務,每完成一小塊任務,就會看看如今主線程是否有空閒,有空閒的話就繼續下一個小任務,沒有空閒那就把主線程讓給瀏覽器或者更高優先級任務。那麼這顆fiber樹的更新就會被停滯,獲得主線程有空了,在繼續渲染。
那麼問題又來,我怎麼知道主線程何時有空?何時沒空?
這個時候咱們想起了requestIdleCallback這個原始api。可是~ react並無用上requestIdleCallback。主要仍是由於瀏覽器的兼容性問題。因此採用了polyfill方案。
詳細源碼解析歡迎閱讀:
React16源碼解析(四)-Scheduler
React16源碼解析(三)-ExpirationTime
注意:上面我構建的fiber樹只是一個虛擬的dom結構,這個fiber樹所有更新好了以後,就會一次性的提交到真實的dom樹上,這個一次性的提交是不能夠中斷的。
提交階段的詳細源碼解析歡迎閱讀:
React16源碼解析(八)-更新流程提交階段
文章若有不妥,歡迎指正~