做爲一個構建用戶界面的庫,React的核心始終圍繞着更新這一個重要的目標,將更新和極致的用戶體驗結合起來是React團隊一直在努力的事情。爲何React能夠將用戶體驗作到這麼好?我想這是基於如下兩點緣由:javascript
本文是對React原理解讀系列的第一篇文章,在正式開始以前,咱們先基於這兩點展開介紹,以便對一些概念能夠先有個基礎認知。java
配合的源碼調試環境在 這裏 ,會跟隨React主要版本進行更新,歡迎隨意下載調試。
Fiber是什麼?它是React的最小工做單元,在React的世界中,一切均可以是組件。在普通的HTML頁面上,人爲地將多個DOM元素整合在一塊兒能夠組成一個組件,HTML標籤能夠是組件(HostComponent),普通的文本節點也能夠是組件(HostText)。每個組件就對應着一個fiber節點,許多個fiber節點互相嵌套、關聯,就組成了fiber樹,正以下面表示的Fiber樹和DOM的關係同樣:react
Fiber樹 DOM樹 div#root div#root | | <App/> div | / \ div p a / ↖ / ↖ p ----> <Child/> | a
一個DOM節點必定對應着一個Fiber節點,但一個Fiber節點卻不必定有對應的DOM節點。git
fiber 做爲工做單元它的結構以下:github
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // Fiber元素的靜態屬性相關 this.tag = tag; this.key = key; // fiber的key this.elementType = null; this.type = null; // fiber對應的DOM元素的標籤類型,div、p... this.stateNode = null; // fiber的實例,類組件場景下,是組件的類,HostComponent場景,是dom元素 // Fiber 鏈表相關 this.return = null; // 指向父級fiber this.child = null; // 指向子fiber this.sibling = null; // 同級兄弟fiber this.index = 0; this.ref = null; // ref相關 // Fiber更新相關 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; // 存儲update的鏈表 this.memoizedState = null; // 類組件存儲fiber的狀態,函數組件存儲hooks鏈表 this.dependencies = null; this.mode = mode; // Effects // flags原爲effectTag,表示當前這個fiber節點變化的類型:增、刪、改 this.flags = NoFlags; this.nextEffect = null; // effect鏈相關,也就是那些須要更新的fiber節點 this.firstEffect = null; this.lastEffect = null; this.lanes = NoLanes; // 該fiber中的優先級,它能夠判斷當前節點是否須要更新 this.childLanes = NoLanes;// 子樹中的優先級,它能夠判斷當前節點的子樹是否須要更新 /* * 能夠當作是workInProgress(或current)樹中的和它同樣的節點, * 能夠經過這個字段是否爲null判斷當前這個fiber處在更新仍是建立過程 * */ this.alternate = null; }
首先要明白,React要完成一次更新分爲兩個階段: render階段和commit階段,兩個階段的工做可分別歸納爲新fiber樹的構建和更新最終效果的應用。segmentfault
render階段其實是在內存中構建一棵新的fiber樹(稱爲workInProgress樹),構建過程是依照現有fiber樹(current樹)從root開始深度優先遍歷再回溯到root的過程,這個過程當中每一個fiber節點都會經歷兩個階段:beginWork和completeWork。組件的狀態計算、diff的操做以及render函數的執行,發生在beginWork階段,effect鏈表的收集、被跳過的優先級的收集,發生在completeWork階段。構建workInProgress樹的過程當中會有一個workInProgress的指針記錄下當前構建到哪一個fiber節點,這是React更新任務可恢復的重要緣由之一。數組
以下面的動圖,就是render階段的簡要過程:
架構
在render階段結束後,會進入commit階段,該階段不可中斷,主要是去依據workInProgress樹中有變化的那些節點(render階段的completeWork過程收集到的effect鏈表),去完成DOM操做,將更新應用到頁面上,除此以外,還會異步調度useEffect以及同步執行useLayoutEffect。dom
這兩個階段都是獨立的React任務,最後會進入Scheduler被調度。render階段採起的調度優先級是依據本次更新的優先級來決定的,以便高優先級任務的介入能夠打斷低優先級任務的工做;commit階段的調度優先級採用的是最高優先級,以保證commit階段同步執行不可被打斷。異步
Scheduler用來調度執行上面提到的React任務。
何爲調度?依據任務優先級來決定哪一個任務先被執行。調度的目標是保證高優先級任務最早被執行。
何爲執行?Scheduler執行任務具有一個特色:即根據時間片去終止任務,並判斷任務是否完成,若未完成則繼續調用任務函數。它只是去作任務的中斷和恢復,而任務是否已經完成則要依賴React告訴它。Scheduler和React相互配合的模式可讓React的任務執行具有異步可中斷的特色。
爲了區分任務的輕重緩急,React內部有一個從事件到調度的優先級機制。事件自己自帶優先級屬性,它致使的更新會基於事件的優先級計算出更新本身的優先級,更新會產生更新任務,更新任務的優先級由更新優先級計算而來,更新任務被調度,因此須要調度優先級去協調調度過程,調度優先級由更新任務優先級計算得出,就這樣一步一步,React將優先級的概念貫穿整個更新的生命週期。
React優先級相關的更多介紹請移步 React中的優先級。
雙緩衝機制是React管理更新工做的一種手段,也是提高用戶體驗的重要機制。
當React開始更新工做以後,會有兩個fiber樹,一個current樹,是當前顯示在頁面上內容對應的fiber樹。另外一個是workInProgress樹,它是依據current樹深度優先遍歷構建出來的新的fiber樹,全部的更新最終都會體如今workInProgress樹上。當更新未完成的時候,頁面上始終展現current樹對應的內容,當更新結束時(commit階段的最後),頁面內容對應的fiber樹會由current樹切換到workInProgress樹,此時workInProgress樹即成爲新的current樹。
function commitRootImpl(root, renderPriorityLevel) { ... // finishedWork即爲workInProgress樹的根節點, // root.current指向它來完成樹的切換 root.current = finishedWork; ... }
兩棵樹在進入commit階段時候的關係以下圖,最終commit階段完成時,兩棵樹會進行切換。
在未更新完成時依舊展現舊內容,保持交互,當更新完成當即切換到新內容,這樣能夠作到新內容和舊內容無縫切換。
本文基本歸納了React大體的工做流程以及角色,本系列文章會以更新過程爲主線,從render階段開始,一直到commit階段,講解React工做的原理。除此以外,會對其餘的重點內容進行大篇幅分析,如事件機制、Scheduler原理、重點Hooks以及context原理。
本系列文章耗時較長,落筆撰寫時,17版本還未發佈,因此參照的源碼版本爲16.13.一、17.0.0-alpha.0以及17共三個版本,我曾經對文章中涉及到的三個版本的代碼進行過覈對,邏輯基本無差異,可放心閱讀。