React Fiber原始碼分析 (介紹)
寫了分析原始碼的文章後, 總覺得缺少了什麼, 在這裡補一個整體的總結,輸出個人的理解~
文章的系列標題為Fiber原始碼分析, 那麼什麼是Fiber,官方給出的解釋是:
React Fiber是對核心演算法的一次重新實現。
ummm, 這樣說實在是有點泛,詳細分析一下
先從開發者角度來看
實際上這次更新對於我們來說影響並不大,只是幾個生命週期改變了,新引入的兩個生命週期函式 getDerivedStateFromProps
,getSnapshotBeforeUpdate
以及在未來 v17.0 版本中即將被移除的三個生命週期函式componentWillMount,componentWillReeiveProps,componentWillUpdate
其他的幾乎沒有任何影響,我們還是照常的寫著原來的程式碼,然後我們就感覺到網頁效能更高了一些。
為什麼網頁效能會變高,Fiber做了什麼?
要回答這個問題,需要回頭看javascript是單執行緒的知識點。
單執行緒一次只能做一件事, 在原來的React中, 如果一次更新的時間比較長,那麼使用者就會感覺到卡頓,也就是丟幀了。
打個比方, 假如我現在要更新1000個元件(往大了說),每個元件平均花時間1ms,那麼在1s內,瀏覽器的整個執行緒都被阻塞了,這時候使用者在input上的任何操作都不會有反應,等到更新完畢,介面上突的一下就顯示了原來使用者的輸入,這個體驗是非常差的。這裡借用官方一張圖, Fiber之前的版本就是這樣,呼叫棧非常深
那麼Fiber,現在是怎麼做呢?
Fiber實際上是把一次更新拆成一個個的單元任務,每次做完一個單元任務後,就詢問是否有更高的優先順序任務,有就去執行,回頭再來幹這件事,如圖
那麼就明白了,Fiber是一個任務調和器!, 同樣,我們根據這個來分析Fiber具體做了什麼
Fiber具體做了什麼
首先,要做到這樣的效果,那麼就需要有以下的功能:
1.可分片 (拆分任務)
2.可中斷 (執行另一個任務後, 可以回頭繼續執行未完成的任務)
3.具備優先順序 (哪個任務先執行)
想要做到拆分任務就需要任務可以分片,也就是React的Fiber,fiber即為一個分片任務,貼上資料結構:
可中斷即是使用了佇列的形式儲存任務, 具體可以看原始碼~
基本是一個fiber即為一個元件,而優先順序即使用fiber的expirationTime屬性, expirationTime越小即優先順序越高
function FiberNode(tag, pendingProps, key, mode) { // Instance this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // Fiber this.return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.firstContextDependency = null; this.mode = mode; // Effects this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; this.expirationTime = NoWork; this.childExpirationTime = NoWork; this.alternate = null; }
從資料結構上, 有幾個屬性值得說一下,
首先是官方註釋了Fiber的幾個屬性, 這幾個是非常重要的
原來的React更新任務是採用遞迴的形式, 那麼現在如果任務想中斷, 在遞迴中是很難做處理的, 所以React改成了大迴圈的模式
修改了生命週期也是因為任務可中斷~
具體可以參考下面這篇文章
到目前為止(React 16.4),React的渲染機制遵循同步渲染: 1) 首次渲染: willMount > render > didMount, 2) props更新時: receiveProps > shouldUpdate > willUpdate > render > didUpdate 3) state更新時: shouldUpdate > willUpdate > render > didUpdate 3) 解除安裝時: willUnmount 期間每個周期函式各司其職,輸入輸出都是可預測,一路下來很順暢。 BUT 從React 17 開始,渲染機制將會發生顛覆性改變,這個新方式就是 Async Render。 首先,async render不是那種服務端渲染,比如發非同步請求到後臺返回newState甚至新的html,這裡的async render還是限制在React作為一個View框架的View層本身。 通過進一步觀察可以發現,預廢棄的三個生命週期函式都發生在虛擬dom的構建期間,也就是render之前。在將來的React 17中,在dom真正render之前,React中的排程機制可能會不定期的去檢視有沒有更高優先順序的任務,如果有,就打斷當前的週期執行函式(哪怕已經執行了一半),等高優先順序任務完成,再回來重新執行之前被打斷的周期函式。這種新機制對現存周期函式的影響就是它們的呼叫時機變的複雜而不可預測,這也就是為什麼”UNSAFE”。 --------------------- 作者:辰辰沉沉大辰沉 來源:CSDN 原文:https://blog.csdn.net/Napoleonxxx/article/details/81120854
什麼是大迴圈?
即執行某個fiber後, 會執行他的子元素, 如果沒有子元素, 則兄弟元素, 然後又回到父元素, 父兄弟元素...
而尋找元素則是根據其上面幾個屬性return(父元素),child(子元素),sibiling(兄弟元素)
假設有以下的程式碼:
<div> <span1></span1> <p> <span2><span2> </p> </div>
他的執行如圖
Fiber的優先順序?
再下來, fiber又是怎麼做到根據優先順序執行任務時不會卡頓呢,如果任務很多, 無窮無盡, 那不是一樣會丟幀?
這時候就是requestIdleCallback這個API的騷操作了, 這個API是幹嘛的呢?
window.requestIdleCallback()
會在瀏覽器空閒時期依次呼叫函式, 這就可以讓開發者在主事件迴圈中執行後臺或低優先順序的任務,而且不會對像動畫和使用者互動這樣延遲觸發而且關鍵的事件產生影響。函式一般會按先進先呼叫的順序執行,除非函式在瀏覽器呼叫它之前就到了它的超時時間。
也就是說React實際上利用這個API在瀏覽器空閒期執行任務, 而這個API的回撥有個引數deadline , 當你超時的時候,無論是不是在空閒期都會執行該任務, 這也就解釋了為什麼React採用時間來做優先順序
不過實際上, React並沒有在版本中使用了這個API,而是通過requestAnimationFrame來hack,強行設定每一幀的到期時間為requestAnimationFrame回撥函式的引數加上33ms
var animationTick = function (rafTime) { isAnimationFrameScheduled = false; ... ... // 每幀到期時間為33ms frameDeadline = rafTime + 33 if (!isIdleScheduled) { isIdleScheduled = true; window.postMessage(messageKey, '*'); } };
當然了, 分優先順序是有一個無法避免的問題, 那就是當有無數的優先順序更高的任務插進來, 就會形成飢餓現象,原有的任務會一直得不到機會執行
後面還有一個打了註釋Effect標籤的幾個屬性,這幾個屬性主要是收集每次更新的結果, 並在最後一層層往上迭代, 最後由最高的節點收集, 並執行更新。
在分析的過程中,發現了React的原始碼中使用了很多鏈式結構, 回撥鏈,任務鏈等, 這個主要是為了增刪時效能比較高
最後總結一下:
React Fiber實際上就是一個任務調和器,它做到了將每一次更新切分成任務分片,從而擁有了可中斷且有優先順序的進行其他任務的功能。
如果想看原始碼, 可以參考本系列的另外三篇文章