摘要:架構理解引用原文是核心算法正在進行的重新實現。構建的過程就是的過程,通過來調度執行一組任務,每完成一個任務后回來看看有沒有插隊的更緊急的,把時間控制權交還給主線程,直到下一次回調再繼續構建。
React Fiber 架構理解
引用原文:React Fiber ArchitectureReact Fiber is an ongoing reimplementation of React"s core algorithm. It is the culmination of over two years of research by the React team.
The goal of React Fiber is to increase its suitability for areas like animation, layout,and gestures. Its headline feature is incremental rendering: the ability to split rendering work into chunks and spread it out over multiple frames.
Other key features include the ability to pause, abort, or reuse work as new updates come in; the ability to assign priority to different types of updates; and new concurrency primitives.
React Fibre 是 React 核心算法正在進行的重新實現。它是 React 團隊兩年多的研究成果。
React Fiber 的目標是提高其對動畫,布局和手勢等領域的適用性。它的主體特征是增量渲染:能夠將渲染工作分割成塊,并將其分散到多個幀中。
其他主要功能包括在進行更新時暫停,中止或重新使用工作的能力,為不同類型的更新分配優先權的能力和新的并發原語。
React16之前組件的渲染邏輯先來看一下react組件渲染時經歷的生命周期:
掛載階段:
constructor()
componentWillMount()
render()
componentDidMount()
更新階段:
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate
卸載階段:
componentWillUnmount()
在之前的版本中,如果你實現一個很復雜的深度嵌套的復合組件,會出現下面的情況:
現有層級關系如下的四個組件:
組件渲染時調用的生命周期順序:
上圖展示的是A,B,C,D的掛載階段調用的生命周期渲染順序,可以看到從頂層組件開始調用各生命周期,一直向下,直至調用完最底層子組件的生命周期。然后再向上調用。
組件更新階段同理。
組件掛載之后,假如修改最上層組件的數據(state),組件更新時的調用棧:
如果這是一個很大,層級很深的組件,可以想像到,組件在渲染時,調用棧過長,再加上如果在期間進行了各種復雜的操作,就可能導致長時間阻塞主線程,react渲染它需要幾十甚至幾百毫秒,這樣的話react就會一直占用瀏覽器主線程,任何其他的操作(包括用戶的點擊,鼠標移動等操作)都無法執行,帶來非常不好的用戶體驗。
React Fiber的出現React Fiber 就是為了解決上面的問題而生。
好似一個潛水員,當它一頭扎進水里,就要往最底層一直游,直到找到最底層的組件,然后他再上岸。在這期間,岸上發生的任何事,都不能對他進行干擾,如果有更重要的事情需要他去做(如用戶操作),也必須得等他上岸。Fiber 本質上是一個虛擬的堆棧幀,新的調度器會按照優先級自由調度這些幀,從而將之前的同步渲染改成了異步渲染,在不影響體驗的情況下去分段計算更新。它讓潛水員會每隔一段時間就上岸,看是否有更重要的事情要做。
對于如何區別優先級,React 有自己的一套邏輯。對于動畫這種實時性很高的東西,也就是 16 ms 必須渲染一次保證不卡頓的情況下,React 會每 16 ms(以內) 暫停一下更新,返回來繼續渲染動畫。
React Fiber 架構 調度拆分為小任務瀏覽器本身也不斷進化中,隨著頁面由簡單的展示轉向WebAPP,它需要一些新能力來承載更多節點的展示與更新。
下面是一些自救措施:
requestAnimationFrame
requestIdleCallback
web worker
IntersectionObserver
react官方采用的是 requestIdleCallback,為了兼容所有平臺,facebook 多帶帶實現了其功能,作為一個獨立的 npm 包使用 react-schedule
其作用是會在瀏覽器空閑時期依次調用函數, 這就可以在主事件循環中執行后臺或低優先級的任務,而且不會對像動畫和用戶交互這樣延遲觸發而且關鍵的事件產生影響。函數一般會按先進先調用的順序執行,除非函數在瀏覽器調用它之前就到了它的超時時間。
簡化后的大致流程圖如下:
Fiber Node 及 Fiber Tree從流程圖上看到會有 Fiber Node 節點,這個是在 react 生成的 Virtual Dom 基礎上增加的一層數據結構,主要是為了將遞歸遍歷轉變成循環遍歷,配合 requestIdleCallback API, 實現任務拆分、中斷與恢復。為了實現循環遍歷,Fiber Node 上攜帶了更多的信息。
每一個 Fiber Node 節點與 Virtual Dom 一一對應,所有 Fiber Node 連接起來形成 Fiber tree, 是個單鏈表樹結構
兩個階段:reconciliation 和 commit對于異步渲染,現在渲染有兩個階段:reconciliation 和 commit 。前者過程是可以打斷的,后者不能暫停,會一直更新界面直到完成。
reconciliation 處理過程
當執行 setState() 或首次 render() 時,進入工作循環,循環體中處理的單元為 Fiber Node, 即是拆分任務的最小單位,從根節點開始,自頂向下逐節點構造 workInProgress tree(構建中的新 Fiber Tree)。
每個工作處理單元做的事情,由 beginWork(), completeUnitOfWork() 兩部分構成。
beginWork()主要做的事情是從頂向下生成所有的 Fiber Node,并標記 Diff, 不包括兄弟節點,每個 Fiber Node 的處理過程根據組件類型略有差異,以 ClassComponent 為例:
1 如果當前節點不需要更新,直接把子節點clone過來,要更新的話標記更新類型
2 更新當前節點狀態(props, state, context等)
3 調用shouldComponentUpdate()
4 調用組件實例方法 render() 獲得新的子節點,并為子節點創建 Fiber Node(創建過程會盡量復用現有 Fiber Node,子節點增刪也發生在這里)
5 如果沒有產生 child fiber,進入下一階段 completeUnitOfWork
completeUnitOfWork() 當沒有子節點,開始遍歷兄弟節點作為下一個處理單元,處理完兄弟節點開始向上回溯,直到再次回去根節點為止,將收集向上回溯過程中的所有 diff,拿到 diff 后開始進入 commit 階段。
構建 workInProgress tree 的過程就是 diff 的過程,通過 requestIdleCallback 來調度執行一組任務,每完成一個任務后回來看看有沒有插隊的(更緊急的),把時間控制權交還給主線程,直到下一次 requestIdleCallback 回調再繼續構建workInProgress tree。
兩個階段涉及到的生命周期:Reconciliation 階段 (React算法,用來比較2顆樹,以確定哪些部分需要重新渲染)
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
Commit 階段 (用于呈現React應用的數據更改。通常是setState的結果。最終導致重新渲染。)
componentDidMount
componentDidUpdate
componentWillUnmount
因為 reconciliation 階段是可以被打斷的,所以 reconciliation 階段會執行的生命周期函數就可能會出現調用多次的情況,從而引起 Bug。所以對于 reconciliation 階段調用的幾個函數,除了 shouldComponentUpdate 以外,其他都應該避免去使用,并且 React16 中也引入了新的 API 來解決這個問題。
于是官方推出了getDerivedStateFromProps,讓你在render設置新state,你主要返回一個新對象,它就主動幫你setState。由于這是一個靜態方法,你不能取到 this,當然你也不能操作instance,這就阻止了你多次操作setState。這樣一來,getDerivedStateFromProps的邏輯應該會很簡單,這樣就不會出錯,不會出錯,就不會打斷DFS過程。
getDerivedStateFromProps取代了原來的componentWillMount與componentWillReceiveProps方法,該函數會在組件 初始化 和 更新 時被調用
class ExampleComponent extends React.Component { // Initialize state in constructor, // Or with a property initializer. state = {}; static getDerivedStateFromProps(nextProps, prevState) { if (prevState.someMirroredValue !== nextProps.someValue) { return { derivedData: computeDerivedState(nextProps), someMirroredValue: nextProps.someValue }; } // Return null to indicate no change to state. return null; } }
在進入commi階段時,組件多了一個新鉤子叫getSnapshotBeforeUpdate,它與commit階段的鉤子一樣只執行一次。
getSnapshotBeforeUpdate 用于替換 componentWillUpdate ,該函數會在 update 后 DOM 更新前被調用,用于讀取最新的 DOM 數據。
于是整個流程變成這樣:(引用大神@司徒正美的圖)
React16 生命周期函數用法建議結合 React Fiber 架構 建議如下使用react生命周期
class ExampleComponent extends React.Component { // 用于初始化 state constructor() {} // 用于替換 `componentWillReceiveProps` ,該函數會在初始化和 `update` 時被調用 // 因為該函數是靜態函數,所以取不到 `this` // 如果需要對比 `prevProps` 需要多帶帶在 `state` 中維護 static getDerivedStateFromProps(nextProps, prevState) {} // 判斷是否需要更新組件,多用于組件性能優化 shouldComponentUpdate(nextProps, nextState) {} // 組件掛載后調用 // 可以在該函數中進行請求或者訂閱 componentDidMount() {} // 用于獲得最新的 DOM 數據 getSnapshotBeforeUpdate() {} // 組件即將銷毀 // 可以在此處移除訂閱,定時器等等 componentWillUnmount() {} // 組件銷毀后調用 componentDidUnMount() {} // 組件更新后調用 componentDidUpdate() {} // 渲染組件函數 render() {} // 以下函數不建議使用 UNSAFE_componentWillMount() {} UNSAFE_componentWillUpdate(nextProps, nextState) {} UNSAFE_componentWillReceiveProps(nextProps) {} }16 大版本主要更新還解決以下痛點:
組件不能返回數組,最見的場合是UL元素下只能使用LI,TR元素下只能使用TD或TH,這時這里有一個組件循環生成LI或TD列表時,我們并不想再放一個DIV,這會破壞HTML的語義。
彈窗問題,之前一直使用不穩定的unstable_renderSubtreeIntoContainer。彈窗是依賴原來DOM樹的上下文,因此這個API第一個參數是組件實例,通過它得到對應虛擬DOM,然后一級級往上找,得到上下文。它的其他參數也很好用,但這個方法一直沒有轉正。。。
異常處理,我們想知道哪個組件出錯,雖然有了React DevTool,但是太深的組件樹查找起來還是很吃力。希望有個方法告訴我出錯位置,并且出錯時能讓我有機會進行一些修復工作
HOC的流行帶來兩個問題,畢竟是社區興起的方案,沒有考慮到ref與context的向下傳遞。
組件的性能優化全憑人肉,并且主要集中在SCU,希望框架能干些事情,即使不用SCU,性能也能上去。
新特性:render / 純組件能夠 return 任何數據結構
CreatePortal API,更好的處理 Dialog 這種場景組件
新的 context api,嘗試代替一部分 redux 的職責
異步渲染/時間切片(time slicing),成倍提高性能
componentDidCatch,錯誤邊界,框架層面上提高用戶 debug 的能力
網絡請求 IO(Suspense),更好的處理異步網絡 IO
參考資料:React Fiber架構
React Fiber Architecture
React系列——React Fiber 架構介紹資料匯總(翻譯+中文資料)
InterviewMap——React周期分析
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103129.html
摘要:因為版本將真正廢棄這三生命周期到目前為止,的渲染機制遵循同步渲染首次渲染,更新時更新時卸載時期間每個周期函數各司其職,輸入輸出都是可預測,一路下來很順暢。通過進一步觀察可以發現,預廢棄的三個生命周期函數都發生在虛擬的構建期間,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段時間準備前端招聘事項...
摘要:在上面我們已經知道瀏覽器是一幀一幀執行的,在兩個執行幀之間,主線程通常會有一小段空閑時間,可以在這個空閑期調用空閑期回調,執行一些任務。另外由于這些堆棧是可以自己控制的,所以可以加入并發或者錯誤邊界等功能。 文章首發于個人博客 前言 2016 年都已經透露出來的概念,這都 9102 年了,我才開始寫 Fiber 的文章,表示慚愧呀。不過現在好的是關于 Fiber 的資料已經很豐富了,...
摘要:它的主體特征是增量渲染能夠將渲染工作分割成塊,并將其分散到多個幀中。實際上,這樣做可能會造成浪費,導致幀丟失并降低用戶體驗。當一個函數被執行時,一個新的堆棧框架被添加到堆棧中。該堆棧框表示由該函數執行的工作。 原文 react-fiber-architecture 介紹 React Fibre是React核心算法正在進行的重新實現。它是React團隊兩年多的研究成果。 React ...
摘要:開始寫代碼構造函數講了那么多的理論,大家一定是暈了,但是沒辦法,架構已經比之前的簡單要復雜太多了,因此不可能指望一次性把的內容全部理解,需要反復多看。 前言 Facebook 的研發能力真是驚人, Fiber 架構給 React 帶來了新視野的同時,將調度一詞介紹給了前端,然而這個架構實在不好懂,比起以前的 Vdom 樹,新的 Fiber 樹就麻煩太多。 可以說,React 16 和 ...
閱讀 1893·2021-11-24 11:16
閱讀 3257·2021-09-10 10:51
閱讀 3180·2021-08-03 14:03
閱讀 1261·2019-08-29 17:03
閱讀 3238·2019-08-29 12:36
閱讀 2218·2019-08-26 14:06
閱讀 493·2019-08-23 16:32
閱讀 2661·2019-08-23 13:42