六個問題讓你更懂 React Fiber
大家好,我是零一,很多人都摸不透 React,看不懂源碼,甚至不想看源碼(確實很難看懂啊!),"霸王硬上弓" 肯定是不行呀,不如從 React 的整體架構或者說從最核心的 Fiber 開始瞭解,說不定能幫你更懂 React 呢!
今天就給大家帶來一篇我同事 獨釣寒江雪 帶來的精品文章,他是思否的優秀作者,輸出多篇硬核文章,感興趣的朋友可以點擊文章末尾的「閱讀原文」進行查看~
作者:獨釣寒江雪
原文:https://segmentfault.com/a/1190000039682751
正文
React Fiber 是 Facebook 花費兩年餘時間對 React 做出的一個重大改變與優化,是對 React 核心算法的一次重新實現。從 Facebook 在 React Conf 2017 會議上確認,React Fiber 會在 React 16 版本發佈至今,也已過去三年有餘,如今,React 17 業已發佈,社區關於 Fiber 的優秀文章不在少數。
本文源於一次團隊內部的技術分享,借鑑社區優秀文章,結合個人理解,進行整合,從六個問題出發,對 React Fiber 進行理解與認識,同時對時下熱門的前端框架 Svelte 進行簡要介紹與剖析,希望對正在探究 React 及各前端框架的小夥伴們能有所助益。
全文大量參考和引用以下幾篇博文,讀者可自行查閱:
-
React 技術揭祕 [1]
-
前端工程師的自我修養:React Fiber 是如何實現更新過程可控的 [2]
-
新興前端框架 Svelte 從入門到原理
-
以 React 爲例,說說框架和性能(下)[3]
一、React 的設計理念是什麼?
React 官網在 React 哲學 [4] 一節開篇提到:
我們認爲,React 是用 JavaScript 構建快速響應的大型 Web 應用程序的首選方式。它在 Facebook 和 Instagram 上表現優秀。React 最棒的部分之一是引導我們思考如何構建一個應用。
由此可見,React 追求的是 “快速響應”,那麼,“快速響應 “的制約因素都有什麼呢?
-
CPU 的瓶頸:當項目變得龐大、組件數量繁多、遇到大計算量的操作或者設備性能不足使得頁面掉幀,導致卡頓。
-
IO 的瓶頸:發送網絡請求後,由於需要等待數據返回才能進一步操作導致不能快速響應。
本文要聊的 fiber 架構主要就是用來解決 CPU 和網絡的問題,這兩個問題一直也是最影響前端開發體驗的地方,一個會造成卡頓,一個會造成白屏。爲此 react 爲前端引入了兩個新概念:Time Slicing 時間分片和 Suspense。
二、React 的 “先天不足” —— 聽說 Vue 3.0 採用了動靜結合的 Dom diff,React 爲何不跟進?
Vue 3.0 動靜結合的 Dom diff
Vue3.0 提出動靜結合的 DOM diff 思想,動靜結合的 DOM diff 其實是在預編譯階段進行了優化。之所以能夠做到預編譯優化,是因爲 Vue core 可以靜態分析 template,在解析模版時,整個 parse 的過程是利用正則表達式順序解析模板,當解析到開始標籤、閉合標籤和文本的時候都會分別執行對應的回調函數,來達到構造 AST 樹的目的。
藉助預編譯過程,Vue 可以做到的預編譯優化就很強大了。比如在預編譯時標記出模版中可能變化的組件節點,再次進行渲染前 diff 時就可以跳過 “永遠不會變化的節點”,而只需要對比 “可能會變化的動態節點”。這也就是動靜結合的 DOM diff 將 diff 成本與模版大小正相關優化到與動態節點正相關的理論依據。
React 能否像 Vue 那樣進行預編譯優化?
Vue 需要做數據雙向綁定,需要進行數據攔截或代理,那它就需要在預編譯階段靜態分析模版,分析出視圖依賴了哪些數據,進行響應式處理。而 React 就是局部重新渲染,React 拿到的或者說掌管的,所負責的就是一堆遞歸 React.createElement 的執行調用(參考下方經過 Babel 轉換的代碼),它無法從模版層面進行靜態分析。JSX 和手寫的 render function[5] 是完全動態的,過度的靈活性導致運行時可以用於優化的信息不足。
JSX 寫法:
<div>
<h1>六個問題助你理解 React Fiber</h1>
<ul>
<li>React</li>
<li>Vue</li>
</ul>
</div>
遞歸 React.createElement:
// Babel轉換後
React.createElement(
"div",
null,
React.createElement(
"h1",
null,
"\u516D\u4E2A\u95EE\u9898\u52A9\u4F60\u7406\u89E3 React Fiber"
),
React.createElement(
"ul",
null,
React.createElement("li", null, "React"),
React.createElement("li", null, "Vue")
)
);
JSX vs Template
jsx and Templates
-
JSX 具有 JavaScript 的完整表現力,可以構建非常複雜的組件。但是靈活的語法,也意味着引擎難以理解,無法預判開發者的用戶意圖,從而難以優化性能。
-
Template 模板是一種非常有約束的語言,你只能以某種方式去編寫模板。
既然存在以上編譯時先天不足,在運行時優化方面,React 一直在努力。比如,React15 實現了 batchedUpdates(批量更新)。即同一事件回調函數上下文中的多次 setState 只會觸發一次更新。
但是,如果單次更新就很耗時,頁面還是會卡頓(這在一個維護時間很長的大應用中是很常見的)。這是因爲 React15 的更新流程是同步執行的,一旦開始更新直到頁面渲染前都不能中斷。
資料參考:以 React 爲例,說說框架和性能(下)[6] | 新興前端框架 Svelte 從入門到原理
三、從架構演變看不斷進擊的 React 都做過哪些優化?
React 渲染頁面的兩個階段
-
調度階段(reconciliation):在這個階段 React 會更新數據生成新的 Virtual DOM,然後通過 Diff 算法,快速找出需要更新的元素,放到更新隊列中去,得到新的更新隊列。
-
渲染階段(commit):這個階段 React 會遍歷更新隊列,將其所有的變更一次性更新到 DOM 上。
React 15 架構
React15 架構可以分爲兩層:
-
Reconciler(協調器)—— 負責找出變化的組件;
-
Renderer(渲染器)—— 負責將變化的組件渲染到頁面上;
在 React15 及以前,Reconciler 採用遞歸的方式創建虛擬 DOM,遞歸過程是不能中斷的。如果組件樹的層級很深,遞歸會佔用線程很多時間,遞歸更新時間超過了 16ms,用戶交互就會卡頓。
爲了解決這個問題,React16 將遞歸的無法中斷的更新重構爲異步的可中斷更新,由於曾經用於遞歸的虛擬 DOM 數據結構已經無法滿足需要。於是,全新的 Fiber 架構應運而生。
React 16 架構
爲了解決同步更新長時間佔用線程導致頁面卡頓的問題,也爲了探索運行時優化的更多可能,React 開始重構並一直持續至今。重構的目標是實現 Concurrent Mode(併發模式)。
從 v15 到 v16,React 團隊花了兩年時間將源碼架構中的 Stack Reconciler 重構爲 Fiber Reconciler。
React16 架構可以分爲三層:
-
Scheduler(調度器)—— 調度任務的優先級,高優任務優先進入 Reconciler;
-
Reconciler(協調器)—— 負責找出變化的組件:更新工作從遞歸變成了可以中斷的循環過程。Reconciler 內部採用了 Fiber 的架構;
-
Renderer(渲染器)—— 負責將變化的組件渲染到頁面上。
React 17 優化
React16 的 expirationTimes 模型只能區分是否>=expirationTimes
決定節點是否更新。React17 的 lanes 模型可以選定一個更新區間,並且動態的向區間中增減優先級,可以處理更細粒度的更新。
Lane 用二進制位表示任務的優先級,方便優先級的計算(位運算),不同優先級佔用不同位置的 “賽道”,而且存在批的概念,優先級越低,“賽道” 越多。高優先級打斷低優先級,新建的任務需要賦予什麼優先級等問題都是 Lane 所要解決的問題。
Concurrent Mode 的目的是實現一套可中斷 / 恢復的更新機制。其由兩部分組成:
-
一套協程架構:Fiber Reconciler
-
基於協程架構的啓發式更新算法:控制協程架構工作方式的算法
資料參考:React17 新特性:啓發式更新算法 [7]
四、瀏覽器一幀都會幹些什麼以及 requestIdleCallback 的啓示
瀏覽器一幀都會幹些什麼?
我們都知道,頁面的內容都是一幀一幀繪製出來的,瀏覽器刷新率代表瀏覽器一秒繪製多少幀。原則上說 1s 內繪製的幀數也多,畫面表現就也細膩。目前瀏覽器大多是 60Hz(60 幀 / s),每一幀耗時也就是在 16.6ms 左右。那麼在這一幀的(16.6ms) 過程中瀏覽器又幹了些什麼呢?
瀏覽器一幀都會幹些什麼
通過上面這張圖可以清楚的知道,瀏覽器一幀會經過下面這幾個過程:
-
接受輸入事件
-
執行事件回調
-
開始一幀
-
執行 RAF (RequestAnimationFrame)
-
頁面佈局,樣式計算
-
繪製渲染
-
執行 RIC (RequestIdelCallback)
第七步的 RIC 事件不是每一幀結束都會執行,只有在一幀的 16.6ms 中做完了前面 6 件事兒且還有剩餘時間,纔會執行。如果一幀執行結束後還有時間執行 RIC 事件,那麼下一幀需要在事件執行結束才能繼續渲染,所以 RIC 執行不要超過 30ms,如果長時間不將控制權交還給瀏覽器,會影響下一幀的渲染,導致頁面出現卡頓和事件響應不及時。
requestIdleCallback 的啓示
我們以瀏覽器是否有剩餘時間作爲任務中斷的標準,那麼我們需要一種機制,當瀏覽器有剩餘時間時通知我們。
requestIdleCallback((deadline) => {
// deadline 有兩個參數
// timeRemaining(): 當前幀還剩下多少時間
// didTimeout: 是否超時
// 另外 requestIdleCallback 後如果跟上第二個參數 {timeout: ...} 則會強制瀏覽器在當前幀執行完後執行。
if (deadline.timeRemaining() > 0) {
// TODO
} else {
requestIdleCallback(otherTasks);
}
});
// 用法示例
var tasksNum = 10000
requestIdleCallback(unImportWork)
function unImportWork(deadline) {
while (deadline.timeRemaining() && tasksNum > 0) {
console.log(`執行了 ${10000 - tasksNum + 1}個任務`)
tasksNum--
}
if (tasksNum > 0) { // 在未來的幀中繼續執行
requestIdleCallback(unImportWork)
}
}
其實部分瀏覽器已經實現了這個 API,這就是 requestIdleCallback。但是由於以下因素,Facebook 拋棄了 requestIdleCallback 的原生 API:
-
瀏覽器兼容性;
-
觸發頻率不穩定,受很多因素影響。比如當我們的瀏覽器切換 tab 後,之前 tab 註冊的 requestIdleCallback 觸發的頻率會變得很低。
參考:requestIdleCallback 的 FPS 只有 20[8]
基於以上原因,在 React 中實現了功能更完備的 requestIdleCallbackpolyfill,這就是 Scheduler。除了在空閒時觸發回調的功能外,Scheduler 還提供了多種調度優先級供任務設置。
資料參考:requestIdleCallback - 後臺任務調度 [9]
五、 Fiber 爲什麼是 React 性能的一個飛躍?
什麼是 Fiber
Fiber 的英文含義是 “纖維”,它是比線程(Thread)更細的線,比線程(Thread)控制得更精密的執行模型。在廣義計算機科學概念中,Fiber 又是一種協作的(Cooperative)編程模型(協程),幫助開發者用一種【既模塊化又協作化】的方式來編排代碼。
在 React 中,Fiber 就是 React 16 實現的一套新的更新機制,讓 React 的更新過程變得可控,避免了之前採用遞歸需要一氣呵成影響性能的做法。
React Fiber 中的時間分片
把一個耗時長的任務分成很多小片,每一個小片的運行時間很短,雖然總時間依然很長,但是在每個小片執行完之後,都給其他任務一個執行的機會,這樣唯一的線程就不會被獨佔,其他任務依然有運行的機會。
React Fiber 把更新過程碎片化,每執行完一段更新過程,就把控制權交還給 React 負責任務協調的模塊,看看有沒有其他緊急任務要做,如果沒有就繼續去更新,如果有緊急任務,那就去做緊急任務。
Stack Reconciler
基於棧的 Reconciler,瀏覽器引擎會從執行棧的頂端開始執行,執行完畢就彈出當前執行上下文,開始執行下一個函數,直到執行棧被清空纔會停止。然後將執行權交還給瀏覽器。由於 React 將頁面視圖視作一個個函數執行的結果。每一個頁面往往由多個視圖組成,這就意味着多個函數的調用。
如果一個頁面足夠複雜,形成的函數調用棧就會很深。每一次更新,執行棧需要一次性執行完成,中途不能幹其他的事兒,只能 " 一心一意 "。結合前面提到的瀏覽器刷新率,JS 一直執行,瀏覽器得不到控制權,就不能及時開始下一幀的繪製。如果這個時間超過 16ms,當頁面有動畫效果需求時,動畫因爲瀏覽器不能及時繪製下一幀,這時動畫就會出現卡頓。不僅如此,因爲事件響應代碼是在每一幀開始的時候執行,如果不能及時繪製下一幀,事件響應也會延遲。
Fiber Reconciler
鏈表結構
在 React Fiber 中用鏈表遍歷的方式替代了 React 16 之前的棧遞歸方案。在 React 16 中使用了大量的鏈表。
- 使用多向鏈表的形式替代了原來的樹結構;
<div id="A">
A1
<div id="B1">
B1
<div id="C1"></div>
</div>
<div id="B2">
B2
</div>
</div>
多向鏈表
- 副作用單鏈表;
副作用單鏈表
- 狀態更新單鏈表;
狀態更新單鏈表
- ...
鏈表是一種簡單高效的數據結構,它在當前節點中保存着指向下一個節點的指針;遍歷的時候,通過操作指針找到下一個元素。
鏈表
鏈表相比順序結構數據格式的好處就是:
-
操作更高效,比如順序調整、刪除,只需要改變節點的指針指向就好了。
-
不僅可以根據當前節點找到下一個節點,在多向鏈表中,還可以找到他的父節點或者兄弟節點。
但鏈表也不是完美的,缺點就是:
-
比順序結構數據更佔用空間,因爲每個節點對象還保存有指向下一個對象的指針。
-
不能自由讀取,必須找到他的上一個節點。
React 用空間換時間,更高效的操作可以方便根據優先級進行操作。同時可以根據當前節點找到其他節點,在下面提到的掛起和恢復過程中起到了關鍵作用。
斐波那契數列的 Fiber
遞歸形式的斐波那契數列寫法:
function fib(n) {
if (n <= 2) {
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
採用 Fiber 的思路將其改寫爲循環(這個例子並不能和 React Fiber 的對等):
function fib(n) {
let fiber = { arg: n, returnAddr: null, a: 0 }, consoled = false;
// 標記循環
rec: while (true) {
// 當展開完全後,開始計算
if (fiber.arg <= 2) {
let sum = 1;
// 尋找父級
while (fiber.returnAddr) {
if(!consoled) {
// 在這裏打印查看形成的鏈表形式的 fiber 對象
consoled=true
console.log(fiber)
}
fiber = fiber.returnAddr;
if (fiber.a === 0) {
fiber.a = sum;
fiber = { arg: fiber.arg - 2, returnAddr: fiber, a: 0 };
continue rec;
}
sum += fiber.a;
}
return sum;
} else {
// 先展開
fiber = { arg: fiber.arg - 1, returnAddr: fiber, a: 0 };
}
}
}
六、React Fiber 是如何實現更新過程可控?
更新過程的可控主要體現在下面幾個方面:
-
任務拆分
-
任務掛起、恢復、終止
-
任務具備優先級
任務拆分
在 React Fiber 機制中,它採用 " 化整爲零 " 的思想,將調和階段(Reconciler)遞歸遍歷 VDOM 這個大任務分成若干小任務,每個任務只負責一個節點的處理。
任務掛起、恢復、終止
workInProgress tree
workInProgress 代表當前正在執行更新的 Fiber 樹。在 render 或者 setState 後,會構建一顆 Fiber 樹,也就是 workInProgress tree,這棵樹在構建每一個節點的時候會收集當前節點的副作用,整棵樹構建完成後,會形成一條完整的副作用鏈。
currentFiber tree
currentFiber 表示上次渲染構建的 Filber 樹。在每一次更新完成後 workInProgress 會賦值給 currentFiber。在新一輪更新時 workInProgress tree 再重新構建,新 workInProgress 的節點通過 alternate 屬性和 currentFiber 的節點建立聯繫。
在新 workInProgress tree 的創建過程中,會同 currentFiber 的對應節點進行 Diff 比較,收集副作用。同時也會複用和 currentFiber 對應的節點對象,減少新創建對象帶來的開銷。也就是說無論是創建還是更新、掛起、恢復以及終止操作都是發生在 workInProgress tree 創建過程中的。workInProgress tree 構建過程其實就是循環的執行任務和創建下一個任務。
掛起
當第一個小任務完成後,先判斷這一幀是否還有空閒時間,沒有就掛起下一個任務的執行,記住當前掛起的節點,讓出控制權給瀏覽器執行更高優先級的任務。
恢復
在瀏覽器渲染完一幀後,判斷當前幀是否有剩餘時間,如果有就恢復執行之前掛起的任務。如果沒有任務需要處理,代表調和階段完成,可以開始進入渲染階段。
- 如何判斷一幀是否有空閒時間的呢?
使用前面提到的 RIC (RequestIdleCallback) 瀏覽器原生 API,React 源碼中爲了兼容低版本的瀏覽器,對該方法進行了 Polyfill。
- 恢復執行的時候又是如何知道下一個任務是什麼呢?
答案是在前面提到的鏈表。在 React Fiber 中每個任務其實就是在處理一個 FiberNode 對象,然後又生成下一個任務需要處理的 FiberNode。
終止
其實並不是每次更新都會走到提交階段。當在調和過程中觸發了新的更新,在執行下一個任務的時候,判斷是否有優先級更高的執行任務,如果有就終止原來將要執行的任務,開始新的 workInProgressFiber 樹構建過程,開始新的更新流程。這樣可以避免重複更新操作。這也是在 React 16 以後生命週期函數 componentWillMount 有可能會執行多次的原因。
workInProgress tree 構建
任務具備優先級
React Fiber 除了通過掛起,恢復和終止來控制更新外,還給每個任務分配了優先級。具體點就是在創建或者更新 FiberNode 的時候,通過算法給每個任務分配一個到期時間(expirationTime)。在每個任務執行的時候除了判斷剩餘時間,如果當前處理節點已經過期,那麼無論現在是否有空閒時間都必須執行該任務。過期時間的大小還代表着任務的優先級。
任務在執行過程中順便收集了每個 FiberNode 的副作用,將有副作用的節點通過 firstEffect、lastEffect、nextEffect 形成一條副作用單鏈表 A1(TEXT)-B1(TEXT)-C1(TEXT)-C1-C2(TEXT)-C2-B1-B2(TEXT)-B2-A。
其實最終都是爲了收集到這條副作用鏈表,有了它,在接下來的渲染階段就通過遍歷副作用鏈完成 DOM 更新。這裏需要注意,更新真實 DOM 的這個動作是一氣呵成的,不能中斷,不然會造成視覺上的不連貫(commit)。
<div id="A1">
A1
<div id="B1">
B1
<div id="C1">C1</div>
<div id="C2">C2</div>
</div>
<div id="B2">
B2
</div>
</div>
副作用鏈
直觀展示
正是基於以上這些過程,使用 Fiber,我們就有了在社區經常看到的兩張對比圖 [10]。
清晰展示及交互、源碼可通過下面兩個鏈接進入,查看網頁源代碼。
-
Stack Example[11]
-
Fiber Example[12]
爲了方便大家對比,我就直接放上兩張對比圖吧,大家自行比對,差別還是很明顯的
Fiber 結構長什麼樣?
基於時間分片的增量更新需要更多的上下文信息,之前的 vDOM tree 顯然難以滿足,所以擴展出了 fiber tree(即 Fiber 上下文的 vDOM tree),更新過程就是根據輸入數據以及現有的 fiber tree 構造出新的 fiber tree(workInProgress tree)。
FiberNode 上的屬性有很多,根據筆者的理解,以下這麼幾個屬性是值得關注的:return、child、sibling(主要負責 fiber 鏈表的鏈接);stateNode;effectTag;expirationTime;alternate;nextEffect。各屬性介紹參看下面的class FiberNode
:
class FiberNode {
constructor(tag, pendingProps, key, mode) {
// 實例屬性
this.tag = tag; // 標記不同組件類型,如函數組件、類組件、文本、原生組件...
this.key = key; // react 元素上的 key 就是 jsx 上寫的那個 key ,也就是最終 ReactElement 上的
this.elementType = null; // createElement的第一個參數,ReactElement 上的 type
this.type = null; // 表示fiber的真實類型 ,elementType 基本一樣,在使用了懶加載之類的功能時可能會不一樣
this.stateNode = null; // 實例對象,比如 class 組件 new 完後就掛載在這個屬性上面,如果是RootFiber,那麼它上面掛的是 FiberRoot,如果是原生節點就是 dom 對象
// fiber
this.return = null; // 父節點,指向上一個 fiber
this.child = null; // 子節點,指向自身下面的第一個 fiber
this.sibling = null; // 兄弟組件, 指向一個兄弟節點
this.index = 0; // 一般如果沒有兄弟節點的話是0 當某個父節點下的子節點是數組類型的時候會給每個子節點一個 index,index 和 key 要一起做 diff
this.ref = null; // reactElement 上的 ref 屬性
this.pendingProps = pendingProps; // 新的 props
this.memoizedProps = null; // 舊的 props
this.updateQueue = null; // fiber 上的更新隊列執行一次 setState 就會往這個屬性上掛一個新的更新, 每條更新最終會形成一個鏈表結構,最後做批量更新
this.memoizedState = null; // 對應 memoizedProps,上次渲染的 state,相當於當前的 state,理解成 prev 和 next 的關係
this.mode = mode; // 表示當前組件下的子組件的渲染方式
// effects
this.effectTag = NoEffect; // 表示當前 fiber 要進行何種更新(更新、刪除等)
this.nextEffect = null; // 指向下個需要更新的fiber
this.firstEffect = null; // 指向所有子節點裏,需要更新的 fiber 裏的第一個
this.lastEffect = null; // 指向所有子節點中需要更新的 fiber 的最後一個
this.expirationTime = NoWork; // 過期時間,代表任務在未來的哪個時間點應該被完成
this.childExpirationTime = NoWork; // child 過期時間
this.alternate = null; // current 樹和 workInprogress 樹之間的相互引用
}
}
fiber-tree
圖片來源:完全理解 React Fiber[13]
function performUnitWork(currentFiber){
//beginWork(currentFiber) //找到兒子,並通過鏈表的方式掛到currentFiber上,每一偶兒子就找後面那個兄弟
//有兒子就返回兒子
if(currentFiber.child){
return currentFiber.child;
}
//如果沒有兒子,則找弟弟
while(currentFiber){//一直往上找
//completeUnitWork(currentFiber);//將自己的副作用掛到父節點去
if(currentFiber.sibling){
return currentFiber.sibling
}
currentFiber = currentFiber.return;
}
}
Concurrent Mode (併發模式)
Concurrent Mode 指的就是 React 利用上面 Fiber 帶來的新特性的開啓的新模式 (mode)。react17 開始支持 concurrent mode,這種模式的根本目的是爲了讓應用保持 cpu 和 io 的快速響應,它是一組新功能,包括 Fiber、Scheduler、Lane,可以根據用戶硬件性能和網絡狀況調整應用的響應速度,核心就是爲了實現異步可中斷的更新。concurrent mode 也是未來 react 主要迭代的方向。
目前 React 實驗版本允許用戶選擇三種 mode:
-
Legacy Mode: 就相當於目前穩定版的模式
-
Blocking Mode: 應該是以後會代替 Legacy Mode 而長期存在的模式
-
Concurrent Mode: 以後會變成 default 的模式
Concurrent Mode 其實開啓了一堆新特性,其中有兩個最重要的特性可以用來解決我們開頭提到的兩個問題:
-
Suspense[14]:Suspense 是 React 提供的一種異步處理的機制, 它不是一個具體的數據請求庫。它是 React 提供的原生的組件異步調用原語。
-
useTrasition[15]:讓頁面實現
Pending -> Skeleton -> Complete
的更新路徑, 用戶在切換頁面時可以停留在當前頁面,讓頁面保持響應。相比展示一個無用的空白頁面或者加載狀態,這種用戶體驗更加友好。
其中 Suspense 可以用來解決請求阻塞的問題,UI 卡頓的問題其實開啓 concurrent mode 就已經解決的,但如何利用 concurrent mode 來實現更友好的交互還是需要對代碼做一番改動的。
資料參考:Concurrent 模式介紹 (實驗性)[16] | 理解 React Fiber & Concurrent Mode[17] | 11.concurrent mode(併發模式是什麼樣的)[18] | 人人都能讀懂的 react 源碼解析 [19]
未來可期
Concurrent Mode 只是併發,既然任務可拆分(只要最終得到完整 effect list 就行),那就允許並行執行,(多個 Fiber reconciler + 多個 worker),首屏也更容易分塊加載 / 渲染(vDOM 森林。
並行渲染的話,據說 Firefox 測試結果顯示,130ms 的頁面,只需要 30ms 就能搞定,所以在這方面是值得期待的,而 React 已經做好準備了,這也就是在 React Fiber 上下文經常聽到的待 unlock 的更多特性之一。
isInputPending —— Fiber 架構思想對前端生態的影響
Facebook 在 Chromium 中提出並實現了 isInputPending() API
,它可以提高網頁的響應能力,但是不會對性能造成太大影響。Facebook 提出的 isInputPending API
是第一個將中斷的概念用於瀏覽器用戶交互的的功能,並且允許 JavaScript 能夠檢查事件隊列而不會將控制權交於瀏覽器。
目前 isInputPending API 僅在 Chromium 的 87 版本開始提供,其他瀏覽器並未實現。
isInputPending
資料參考:Facebook 將對 React 的優化實現到了瀏覽器![20]
Svelte 對固有模式的衝擊
當下前端領域,三大框架 React、Vue、Angular 版本逐漸穩定,如果說前端行業會出現哪些框架有可能會挑戰 React 或者 Vue 呢?很多人認爲 Svelte 應該是其中的選項之一。
Svelte 叫法是[Svelte]
, 本意是苗條纖瘦的,是一個新興熱門的前端框架。在開發者滿意度、興趣度、市場佔有率上均名列前茅,同時,它有更小的打包體積,更少的開發代碼書寫,在性能測評中,與 React、Vue 相比,也不遑多讓。
Svelte 的核心思想在於『通過靜態編譯減少框架運行時的代碼量』。
Svelte 優勢有哪些
-
No Runtime —— 無運行時代碼
-
Less-Code —— 寫更少的代碼
-
Hight-Performance —— 高性能
Svelte 劣勢
-
社區
-
社區
-
社區
原理概覽
Svelte 在編譯時,就已經分析好了數據 和 DOM 節點之間的對應關係,在數據發生變化時,可以非常高效的來更新 DOM 節點。
-
Rich Harris 在進行 Svelte 的設計的時候沒有采用 Virtual DOM,主要是因爲他覺得 Virtual DOM Diff 的過程是非常低效的。具體可參考 Virtual Dom 真的高效嗎 [21] 一文;Svelte 採用了 Templates 語法,在編譯的過程中就進行優化操作;
-
Svelte 記錄髒數據的方式:位掩碼(bitMask);
-
數據和 DOM 節點之間的對應關係:React 和 Vue 是通過 Virtual Dom 進行 diff 來算出來更新哪些 DOM 節點效率最高。Svelte 是在編譯時候,就記錄了數據 和 DOM 節點之間的對應關係,並且保存在 p 函數中。
數據和 DOM 節點之間的對應關係
資料參考:[新興前端框架 Svelte 從入門到原理](
參考資料
[1]
React 技術揭祕: https://link.segmentfault.com/?url=https%3A%2F%2Freact.iamkasong.com%2F
[2]
前端工程師的自我修養:React Fiber 是如何實現更新過程可控的: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.zoo.team%2Farticle%2Fabout-react-fiber
[3]
以 React 爲例,說說框架和性能(下): https://link.segmentfault.com/?url=https%3A%2F%2Fgitbook.cn%2Fm%2Fmazi%2Fcolumns%2F5c91c813968b1d64b1e08fde%2Ftopics%2F5cbbf49bbbbba80861a35c64
[4]
React 哲學: https://link.segmentfault.com/?url=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Fthinking-in-react.html
[5]
JSX 和手寫的 render function: https://link.segmentfault.com/?url=https%3A%2F%2Fcn.vuejs.org%2Fv2%2Fguide%2Frender-function.html
[6]
以 React 爲例,說說框架和性能(下): https://link.segmentfault.com/?url=https%3A%2F%2Fgitbook.cn%2Fm%2Fmazi%2Fcolumns%2F5c91c813968b1d64b1e08fde%2Ftopics%2F5cbbf49bbbbba80861a35c64
[7]
React17 新特性:啓發式更新算法: https://link.segmentfault.com/?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F182411298
[8]
requestIdleCallback 的 FPS 只有 20: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Ffacebook%2Freact%2Fissues%2F13206
[9]
requestIdleCallback - 後臺任務調度: https://link.segmentfault.com/?url=http%3A%2F%2Fwww.zhangyunling.com%2F702.html
[10]
兩張對比圖: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2F
[11]
Stack Example: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2Fstack.html
[12]
Fiber Example: https://link.segmentfault.com/?url=https%3A%2F%2Fclaudiopro.github.io%2Freact-fiber-vs-stack-demo%2Ffiber.html
[13]
完全理解 React Fiber: https://link.segmentfault.com/?url=http%3A%2F%2Fwww.ayqy.net%2Fblog%2Fdive-into-react-fiber%2F
[14]
Suspense: https://link.segmentfault.com/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6844903981999718407
[15]
useTrasition: https://link.segmentfault.com/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6844903986420514823
[16]
Concurrent 模式介紹 (實驗性): https://link.segmentfault.com/?url=https%3A%2F%2Fzh-hans.reactjs.org%2Fdocs%2Fconcurrent-mode-intro.html
[17]
理解 React Fiber & Concurrent Mode: https://link.segmentfault.com/?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F109971435
[18]
11.concurrent mode(併發模式是什麼樣的): https://link.segmentfault.com/?url=https%3A%2F%2Fxiaochen1024.com%2Farticle_item%2F600acd69245877002ed5df05
[19]
人人都能讀懂的 react 源碼解析: https://link.segmentfault.com/?url=https%3A%2F%2Fxiaochen1024.com%2F
[20]
Facebook 將對 React 的優化實現到了瀏覽器!: https://link.segmentfault.com/?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FLbcu1aa2LQZlddAwIIExqA
[21]
Virtual Dom 真的高效嗎: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.sveltejs.cn%2Fblog%2Fvirtual-dom-is-pure-overhead
寫在最後
我是零一,如果我的文章對你有幫助,請點個 贊👍🏻 支持我一下
若有什麼疑問,也可以加我微信:Lpyexplore333
,一起交流心得
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/WpkXrCbgaR2814hnwe5SEw