成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

React Concurrent Mode三連:是什么/為什么/怎么做

開發 前端
本文會詳細介紹Concurrent Mode的來龍去脈,以及這套體系從底層架構到上層API的實現。

[[350833]]

 最近發布的React v17.0沒有包含新特性。

究其原因,v17.0主要的工作在于源碼內部對Concurrent Mode的支持。所以v17版本也被稱為“墊腳石”版本。

本文會詳細介紹Concurrent Mode的來龍去脈,以及這套體系從底層架構到上層API的實現。

由于跨度比較長,細節難免缺失。對文中提到的細節的進一步補足,歡迎關注我的工粽號 —— 魔術師卡頌,給你一份完整的源碼學習方案。

是什么?

Concurrent Mode是什么?你可以從官網Concurrent 模式介紹[1]了解其基本概念。

一句話概括:

  • Concurrent 模式是一組 React 的新功能,可幫助應用保持響應,并根據用戶的設備性能和網速進行適當的調整。

為了讓應用保持響應,我們需要先了解是什么在制約應用保持響應?

我們日常使用App,瀏覽網頁時,有兩類場景會制約保持響應:

  • 當遇到大計算量的操作或者設備性能不足使頁面掉幀,導致卡頓。
  • 發送網絡請求后,由于需要等待數據返回才能進一步操作導致不能快速響應。

這兩類場景可以概括為:

  • CPU的瓶頸
  • IO的瓶頸

CPU的瓶頸

當項目變得龐大、組件數量繁多時,就容易遇到CPU的瓶頸。

考慮如下Demo,我們向視圖中渲染3000個li:

  1. function App() { 
  2.   const len = 3000; 
  3.   return ( 
  4.     <ul> 
  5.       {Array(len).fill(0).map((_, i) => <li>{i}</li>)} 
  6.     </ul> 
  7.   ); 
  8.  
  9. const rootEl = document.querySelector("#root"); 
  10. ReactDOM.render(<App/>, rootEl);   

主流瀏覽器刷新頻率為60Hz,即每(1000ms / 60Hz)16.6ms瀏覽器刷新一次。

我們知道,JS可以操作DOM,GUI渲染線程與JS線程是互斥的。所以JS腳本執行和瀏覽器布局、繪制不能同時執行。

在每16.6ms時間內,需要完成如下工作:

  1. JS腳本執行 -----  樣式布局 ----- 樣式繪制 

當JS執行時間過長,超出了16.6ms,這次刷新就沒有時間執行樣式布局和樣式繪制了。

在Demo中,由于組件數量繁多(3000個),JS腳本執行時間過長,頁面掉幀,造成卡頓。

可以從打印的執行堆棧圖看到,JS執行時間為73.65ms,遠遠多于一幀的時間。

如何解決這個問題呢?

答案是:在瀏覽器每一幀的時間中,預留一些時間給JS線程,React利用這部分時間更新組件(可以看到,在源碼[2]中,預留的初始時間是5ms)。

當預留的時間不夠用時,React將線程控制權交還給瀏覽器使其有時間渲染UI,React則等待下一幀時間到來繼續被中斷的工作。

  • 這種將長任務分拆到每一幀中,像螞蟻搬家一樣一次執行一小段任務的操作,被稱為時間切片(time slice)

所以,解決CPU瓶頸的關鍵是實現時間切片,而時間切片的關鍵是:將同步的更新變為可中斷的異步更新。

IO的瓶頸

網絡延遲是前端開發者無法解決的。如何在網絡延遲客觀存在的情況下,減少用戶對網絡延遲的感知?

React給出的答案是將人機交互研究的結果整合到真實的 UI 中[3]。

這里我們以業界人機交互最頂尖的蘋果舉例,在IOS系統中:

點擊“設置”面板中的“通用”,進入“通用”界面:

作為對比,再點擊“設置”面板中的“Siri與搜索”,進入“Siri與搜索”界面:

你能感受到兩者體驗上的區別么?

事實上,點擊“通用”后的交互是同步的,直接顯示后續界面。

而點擊“Siri與搜索”后的交互是異步的,需要等待請求返回后再顯示后續界面。

但從用戶感知來看,這兩者的區別微乎其微。

這里的竅門在于:點擊“Siri與搜索”后,先在當前頁面停留了一小段時間,這一小段時間被用來請求數據。

當“這一小段時間”足夠短時,用戶是無感知的。如果請求時間超過一個范圍,再顯示loading的效果。

試想如果我們一點擊“Siri與搜索”就顯示loading效果,即使數據請求時間很短,loading效果一閃而過。用戶也是可以感知到的。

為此,React實現了Suspense[4]、useDeferredValue[5]。

在源碼內部,為了支持這些特性,同樣需要將同步的更新變為可中斷的異步更新。

Concurrent Mode自底向上

底層基礎決定了上層API的實現,接下來讓我們了解下,Concurrent Mode自底向上都包含哪些組成部分,才能實現上文提到的功能。

底層架構 —— Fiber架構

從上文我們了解到,為了解決CPU、IO瓶頸,最關鍵的一點是:實現異步可中斷的更新。

基于這個前提,React花費2年時間重構完成了Fiber架構。

Fiber機構的意義在于,他將單個組件作為工作單元,使以組件為粒度的“異步可中斷的更新”成為可能。

架構的驅動力 —— Scheduler

如果我們同步運行Fiber架構(通過ReactDOM.render),則Fiber架構與重構前并無區別。

但是當我們配合時間切片,就能根據宿主環境性能,為每個工作單元分配一個可運行時間,實現“異步可中斷的更新”。

于是,scheduler[6](調度器)產生了。

Scheduler能保證我們的長任務被拆分到每一幀不同的task中。

當我們為上文講到的渲染3000個li的Demo開啟Concurrent Mode:

  1. // 通過使用ReactDOM.unstable_createRoot開啟Concurrent Mode 
  2. // ReactDOM.render(<App/>, rootEl);   
  3. ReactDOM.unstable_createRoot(rootEl).render(<App/>); 

可以看到,每段JS腳本執行時間大體在5ms左右。

這樣瀏覽器就有剩余時間執行樣式布局和樣式繪制,減少掉幀的可能性。

Fiber架構配合Scheduler實現了Concurrent Mode的底層剛需 —— “異步可中斷的更新”。

架構運行策略 —— lane模型

到目前為止,通過Scheduler,React可以控制更新在Fiber架構中運行/中斷/繼續運行。

基于當前的架構,當一次更新在運行過程中被中斷,過段時間再繼續運行,這就是“異步可中斷的更新”。

當一次更新在運行過程中被中斷,轉而重新開始一次新的更新,我們可以說:后一次更新打斷了前一次更新。

這就是優先級的概念:后一次更新的優先級更高,他打斷了正在進行的前一次更新。

多個優先級之間如何互相打斷?優先級能否升降?本次更新應該賦予什么優先級?

這就需要一個模型控制不同優先級之間的關系與行為,于是lane模型誕生了。

  • lane模型通過將不同優先級賦值給一個位,通過31位的位運算來操作優先級

如下是不同優先級的定義:

  1. export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000; 
  2. export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000; 
  3.  
  4. export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001; 
  5. export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010; 
  6.  
  7. export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100; 
  8. const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000; 
  9.  
  10. // 省略... 

上層實現

現在,我們可以說:

  • 從源碼層面講,Concurrent Mode是一套可控的“多優先級更新架構”。

那么基于該架構之上可以實現哪些有意思的功能?我們舉幾個例子:

batchedUpdates

如果我們在一次事件回調中觸發多次更新,他們會被合并為一次更新進行處理。

如下代碼執行只會觸發一次更新:

  1. onClick() { 
  2.   this.setState({stateA: 1}); 
  3.   this.setState({stateB: false}); 
  4.   this.setState({stateA: 2}); 

這種合并多個更新的優化方式被稱為batchedUpdates。

batchedUpdates在很早的版本就存在了,不過之前的實現局限很多(脫離當前上下文環境的更新不會被合并)。

在Concurrent Mode中,是以優先級為依據對更新進行合并的,使用范圍更廣。

Suspense

Suspense[7]可以在組件請求數據時展示一個pending狀態。請求成功后渲染數據。

本質上講Suspense內的組件子樹比組件樹的其他部分擁有更低的優先級。

useDeferredValue

useDeferredValue[8]返回一個延遲響應的值,該值可能“延后”的最長時間為timeoutMs。

例子:

  1. const deferredValue = useDeferredValue(value, { timeoutMs: 2000 }); 

在useDeferredValue內部會調用useState并觸發一次更新。

這次更新的優先級很低,所以當前如果有正在進行中的更新,不會受useDeferredValue產生的更新影響。所以useDeferredValue能夠返回延遲的值。

當超過timeoutMs后useDeferredValue產生的更新還沒進行(由于優先級太低一直被打斷),則會再觸發一次高優先級更新。

總結

除了以上介紹的實現,可以預見,當v17完美支持Concurrent Mode后,v18會迎來一大波基于Concurrent Mode的庫。

 

責任編輯:姜華 來源: 魔術師卡頌
相關推薦

2024-12-04 08:53:44

Docker腳本命令

2020-07-28 08:09:02

領域驅動設計

2021-11-17 07:44:29

React 前端 組件

2022-05-27 20:53:42

數字化轉型數字化經營消費者

2021-08-04 08:33:25

React服務端渲染

2023-07-29 22:27:44

2019-10-14 13:20:26

物聯網數據IOT

2017-06-16 16:22:41

機房墻面

2017-10-25 09:50:51

Linux

2015-07-23 09:20:19

mmap

2021-12-16 22:43:52

運營商手機號卡數據

2018-01-08 14:18:14

代碼互聯網持續集成

2021-03-14 15:17:13

前端開發架構

2023-05-09 07:16:06

2018-02-07 00:00:00

數字化轉型

2018-08-02 15:24:05

RPCJava微服務

2022-07-14 07:17:11

LXCDocker語言

2018-07-18 15:02:54

混合云云戰略安全

2018-07-09 14:44:27

存儲

2023-04-04 07:15:01

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品无 | 另类一区 | 久久精品小视频 | 日韩欧美国产不卡 | 国产激情视频网 | 亚洲国产精久久久久久久 | 日韩精品视频中文字幕 | 国产99久久久国产精品 | 国产精品一区二区福利视频 | 亚洲精品 在线播放 | 精品国产黄a∨片高清在线 www.一级片 国产欧美日韩综合精品一区二区 | 黄色精品 | 色综合天天综合网国产成人网 | 国产在线色 | 在线欧美日韩 | 国产免费一区二区 | 爱高潮www亚洲精品 中文字幕免费视频 | 一区二区三区欧美在线 | 亚洲高清在线 | 亚洲欧美一区二区三区1000 | 在线成人av | 国产一区二区在线视频 | 中文字幕一区二区三区四区五区 | 亚洲午夜精品一区二区三区他趣 | 久久极品 | 精品不卡 | 亚洲欧美中文字幕在线观看 | 国产一区影院 | 成人免费网站 | 久久亚洲美女 | 精品在线视频播放 | 日本成人在线观看网站 | 久久久久久久久久久高潮一区二区 | 综合精品久久久 | 日韩一区二区在线视频 | 久久久成人动漫 | 在线一级片 | 欧美淫片| 中文字幕 视频一区 | 欧美成年视频 | 亚洲欧美中文日韩在线 |