React Fiber源碼分析 第四篇(概括總結)

系列文章

React Fiber源碼分析 第一篇
React Fiber源碼分析 第二篇(同步模式)
React Fiber源碼分析 第三篇(異步狀態)
React Fiber源碼分析 第四篇(概括總結)javascript

前言

React Fiber是React在V16版本中的大更新,利用了閒餘時間看了一些源碼,作個小記錄~java

什麼是Fiber

從開發者角度來看

實際上此次更新對於咱們來講影響並不大,只是幾個生命週期改變了(React在版本中的更新簡直作到了像一門語言同樣,完美的兼容老版本,底層算法的大重構對於開發者來講徹底透明),新引入的兩個生命週期函數 getDerivedStateFromProps,getSnapshotBeforeUpdate 以及在將來 v17.0 版本中即將被移除的三個生命週期函數componentWillMount,componentWillReeiveProps,componentWillUpdate,目前版本並不會影響原生命週期的使用,但不能和新的生命週期一塊兒使用,也會被標記爲不安全,下圖爲目前React的流程圖算法

image

其餘的幾乎沒有任何影響,咱們仍是照常的寫着原來的代碼,而後咱們就感受到網頁性能更高了一些。瀏覽器

爲何網頁性能會變高

要回答這個問題,須要回頭看javascript是單線程的知識點。安全

單線程一次只能作一件事, 在原來的React中, 若是一次更新的時間比較長,那麼用戶就會感受到卡頓,也就是丟幀了。bash

打個比方, 假如我如今要更新1000個組件(往大了說),每一個組件平均花時間1ms,那麼在1s內,瀏覽器的整個線程都被阻塞了,這時候用戶在input上的任何操做都不會有反應,等到更新完畢,界面上突的一下就顯示了原來用戶的輸入,這個體驗是很是差的。這裏借用官方一張圖, Fiber以前的版本就是這樣,調用棧很是深
網絡

image

那麼Fiber,如今是怎麼作呢?數據結構

Fiber其實是把一次更新拆成一個個的單元任務,每次作完一個單元任務後,就詢問是否有更高的優先級任務,有就去執行,回頭再來幹這件事,如圖
異步

image

那麼就明白了,Fiber是一個任務調和器!, 一樣,咱們根據這個來分析Fiber具體作了什麼函數

Fiber具體作了什麼

首先,要作到這樣的效果,那麼就須要有如下的功能:

  1. 任務可分片 (拆分任務)
  2. 任務可中斷 (執行另外一個任務後, 能夠回頭繼續執行未完成的任務)
  3. 具有優先級 (哪一個任務先執行)

任務可分片

在React中,不管是state仍是props的更新, 最後都操做在JSX的標籤上
利用這種自然友好的表達,直接把每個標籤當成一個任務分片如:div、p一、p二、span都是一個任務分片

<div>
  <p>p1</p>
  <p>
    <span>p2</span>
  </p>
</div>
複製代碼

固然, 還要從標籤轉換成VDOM,再轉成Fiber,纔是一個真正的任務片,如圖:

fiber的數據結構

任務可中斷

Fiber以前React是經過棧調度器進行遞歸更新,畢竟標籤化是自然嵌套的,對遞歸友好,可是遞歸很差break和continue

從大遞歸到大循環

Fiber則是以鏈表的形式來進行逐步更新(深度優先遍歷算法),鏈表對break和continue友好Fiber節點擁有return, child, sibling三個屬性,分別對應父節點, 第一個孩子, 它右邊的兄弟,


(圖來自網絡,侵刪)

如何回到中斷
任務中斷,執行高優先級任務後如何回來被中斷的任務

React內部維護一個任務鏈表,每次某個任務結束後都會刪除已完成的任務並繼續執行其餘可執行的任務,每一個任務都有一個finishedWork屬性,若是該屬性不爲null,則說明更新完畢,只差commit render階段

回到中斷任務後,如何從中斷的任務片開始

這個主要依賴於fiber中的兩個屬性expirationTime和childExpirationTime,當某個fiber被執行完畢後,會把expirationTime設爲NoWork,即被打斷後能夠經過該屬性判斷任務碎片是否 須要執行

this.expirationTime = NoWork  // 任務優先級
this.childExpirationTime = NoWork // 子任務片的優先級
複製代碼
任務中斷再執行的流程
  1. 經過深度遍歷搜索算法對每個fiber即任務碎片進行更新
  2. 每個任務碎片完成後會將expirationTime設爲NoWork
  3. 假設此時有更高優先級的任務,則執行更高優先級任務
  4. 任務執行完成後,會從任務列表中剔除,並繼續執行其餘未完成且能夠執行的任務。
  5. 回到被打斷任務,能夠經過任務的finishWork屬性判斷是否須要執行更新
  6. 根據任務碎片的expirationTime判斷是否須要執行更新
中斷更新階段其餘屬性介紹
Alternater

每次更新都不會對fiber直接操做,而是克隆一個做爲alternater屬性

updateQueue

更新隊列, 存放更新的信息

Effect

收集更新信息,生成真實DOM

具有優先級

每一個Root任務\更新任務\fiber都具備expirationTime屬性,該屬性即爲優先級expirationTime越小,優先級越高,同步模式下該值爲0, 每一個層級的任務都是以鏈表的形式存在

爲何採用時間做爲優先級屬性

這時候就是requestIdleCallback這個API的騷操做了, 這個API是幹嗎的呢?

window.requestIdleCallback()會在瀏覽器空閒時期依次調用函數, 這就可讓開發者在主事件循環中執行後臺或低優先級的任務,並且不會對像動畫和用戶交互這樣延遲觸發並且關鍵的事件產生影響。函數通常會按先進先調用的順序執行,除非函數在瀏覽器調用它以前就到了它的超時時間。

image

也就是說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, '*');
    }
  };
複製代碼

固然了, 分優先級是有一個沒法避免的問題, 那就是當有無數的優先級更高的任務插進來, 就會造成飢餓現象,原有的任務會一直得不到機會執行

總結

React Fiber實際上就是一個任務調和器,它作到了將每一次更新切分紅任務分片,從而擁有了可中斷且有優先級的進行其餘任務的功能。 在分析的過程當中,發現了React的源碼中使用了不少鏈式結構, 回調鏈,任務鏈等,這個主要是爲了增刪時性能比較高

相關文章
相關標籤/搜索