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

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

開(kāi)發(fā) 前端
本文會(huì)詳細(xì)介紹Concurrent Mode的來(lái)龍去脈,以及這套體系從底層架構(gòu)到上層API的實(shí)現(xiàn)。

[[350833]]

 最近發(fā)布的React v17.0沒(méi)有包含新特性。

究其原因,v17.0主要的工作在于源碼內(nèi)部對(duì)Concurrent Mode的支持。所以v17版本也被稱(chēng)為“墊腳石”版本。

本文會(huì)詳細(xì)介紹Concurrent Mode的來(lái)龍去脈,以及這套體系從底層架構(gòu)到上層API的實(shí)現(xiàn)。

由于跨度比較長(zhǎng),細(xì)節(jié)難免缺失。對(duì)文中提到的細(xì)節(jié)的進(jìn)一步補(bǔ)足,歡迎關(guān)注我的工粽號(hào) —— 魔術(shù)師卡頌,給你一份完整的源碼學(xué)習(xí)方案。

是什么?

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

一句話概括:

  • Concurrent 模式是一組 React 的新功能,可幫助應(yīng)用保持響應(yīng),并根據(jù)用戶(hù)的設(shè)備性能和網(wǎng)速進(jìn)行適當(dāng)?shù)恼{(diào)整。

為了讓?xiě)?yīng)用保持響應(yīng),我們需要先了解是什么在制約應(yīng)用保持響應(yīng)?

我們?nèi)粘J褂肁pp,瀏覽網(wǎng)頁(yè)時(shí),有兩類(lèi)場(chǎng)景會(huì)制約保持響應(yīng):

  • 當(dāng)遇到大計(jì)算量的操作或者設(shè)備性能不足使頁(yè)面掉幀,導(dǎo)致卡頓。
  • 發(fā)送網(wǎng)絡(luò)請(qǐng)求后,由于需要等待數(shù)據(jù)返回才能進(jìn)一步操作導(dǎo)致不能快速響應(yīng)。

這兩類(lèi)場(chǎng)景可以概括為:

  • CPU的瓶頸
  • IO的瓶頸

CPU的瓶頸

當(dāng)項(xiàng)目變得龐大、組件數(shù)量繁多時(shí),就容易遇到CPU的瓶頸。

考慮如下Demo,我們向視圖中渲染3000個(gè)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腳本執(zhí)行和瀏覽器布局、繪制不能同時(shí)執(zhí)行。

在每16.6ms時(shí)間內(nèi),需要完成如下工作:

  1. JS腳本執(zhí)行 -----  樣式布局 ----- 樣式繪制 

當(dāng)JS執(zhí)行時(shí)間過(guò)長(zhǎng),超出了16.6ms,這次刷新就沒(méi)有時(shí)間執(zhí)行樣式布局和樣式繪制了。

在Demo中,由于組件數(shù)量繁多(3000個(gè)),JS腳本執(zhí)行時(shí)間過(guò)長(zhǎng),頁(yè)面掉幀,造成卡頓。

可以從打印的執(zhí)行堆棧圖看到,JS執(zhí)行時(shí)間為73.65ms,遠(yuǎn)遠(yuǎn)多于一幀的時(shí)間。

如何解決這個(gè)問(wèn)題呢?

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

當(dāng)預(yù)留的時(shí)間不夠用時(shí),React將線程控制權(quán)交還給瀏覽器使其有時(shí)間渲染UI,React則等待下一幀時(shí)間到來(lái)繼續(xù)被中斷的工作。

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

所以,解決CPU瓶頸的關(guān)鍵是實(shí)現(xiàn)時(shí)間切片,而時(shí)間切片的關(guān)鍵是:將同步的更新變?yōu)榭芍袛嗟漠惒礁隆?/p>

IO的瓶頸

網(wǎng)絡(luò)延遲是前端開(kāi)發(fā)者無(wú)法解決的。如何在網(wǎng)絡(luò)延遲客觀存在的情況下,減少用戶(hù)對(duì)網(wǎng)絡(luò)延遲的感知?

React給出的答案是將人機(jī)交互研究的結(jié)果整合到真實(shí)的 UI 中[3]。

這里我們以業(yè)界人機(jī)交互最頂尖的蘋(píng)果舉例,在IOS系統(tǒng)中:

點(diǎn)擊“設(shè)置”面板中的“通用”,進(jìn)入“通用”界面:

作為對(duì)比,再點(diǎn)擊“設(shè)置”面板中的“Siri與搜索”,進(jìn)入“Siri與搜索”界面:

你能感受到兩者體驗(yàn)上的區(qū)別么?

事實(shí)上,點(diǎn)擊“通用”后的交互是同步的,直接顯示后續(xù)界面。

而點(diǎn)擊“Siri與搜索”后的交互是異步的,需要等待請(qǐng)求返回后再顯示后續(xù)界面。

但從用戶(hù)感知來(lái)看,這兩者的區(qū)別微乎其微。

這里的竅門(mén)在于:點(diǎn)擊“Siri與搜索”后,先在當(dāng)前頁(yè)面停留了一小段時(shí)間,這一小段時(shí)間被用來(lái)請(qǐng)求數(shù)據(jù)。

當(dāng)“這一小段時(shí)間”足夠短時(shí),用戶(hù)是無(wú)感知的。如果請(qǐng)求時(shí)間超過(guò)一個(gè)范圍,再顯示loading的效果。

試想如果我們一點(diǎn)擊“Siri與搜索”就顯示loading效果,即使數(shù)據(jù)請(qǐng)求時(shí)間很短,loading效果一閃而過(guò)。用戶(hù)也是可以感知到的。

為此,React實(shí)現(xiàn)了Suspense[4]、useDeferredValue[5]。

在源碼內(nèi)部,為了支持這些特性,同樣需要將同步的更新變?yōu)榭芍袛嗟漠惒礁隆?/p>

Concurrent Mode自底向上

底層基礎(chǔ)決定了上層API的實(shí)現(xiàn),接下來(lái)讓我們了解下,Concurrent Mode自底向上都包含哪些組成部分,才能實(shí)現(xiàn)上文提到的功能。

底層架構(gòu) —— Fiber架構(gòu)

從上文我們了解到,為了解決CPU、IO瓶頸,最關(guān)鍵的一點(diǎn)是:實(shí)現(xiàn)異步可中斷的更新。

基于這個(gè)前提,React花費(fèi)2年時(shí)間重構(gòu)完成了Fiber架構(gòu)。

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

架構(gòu)的驅(qū)動(dòng)力 —— Scheduler

如果我們同步運(yùn)行Fiber架構(gòu)(通過(guò)ReactDOM.render),則Fiber架構(gòu)與重構(gòu)前并無(wú)區(qū)別。

但是當(dāng)我們配合時(shí)間切片,就能根據(jù)宿主環(huán)境性能,為每個(gè)工作單元分配一個(gè)可運(yùn)行時(shí)間,實(shí)現(xiàn)“異步可中斷的更新”。

于是,scheduler[6](調(diào)度器)產(chǎn)生了。

Scheduler能保證我們的長(zhǎng)任務(wù)被拆分到每一幀不同的task中。

當(dāng)我們?yōu)樯衔闹v到的渲染3000個(gè)li的Demo開(kāi)啟Concurrent Mode:

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

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

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

Fiber架構(gòu)配合Scheduler實(shí)現(xiàn)了Concurrent Mode的底層剛需 —— “異步可中斷的更新”。

架構(gòu)運(yùn)行策略 —— lane模型

到目前為止,通過(guò)Scheduler,React可以控制更新在Fiber架構(gòu)中運(yùn)行/中斷/繼續(xù)運(yùn)行。

基于當(dāng)前的架構(gòu),當(dāng)一次更新在運(yùn)行過(guò)程中被中斷,過(guò)段時(shí)間再繼續(xù)運(yùn)行,這就是“異步可中斷的更新”。

當(dāng)一次更新在運(yùn)行過(guò)程中被中斷,轉(zhuǎn)而重新開(kāi)始一次新的更新,我們可以說(shuō):后一次更新打斷了前一次更新。

這就是優(yōu)先級(jí)的概念:后一次更新的優(yōu)先級(jí)更高,他打斷了正在進(jìn)行的前一次更新。

多個(gè)優(yōu)先級(jí)之間如何互相打斷?優(yōu)先級(jí)能否升降?本次更新應(yīng)該賦予什么優(yōu)先級(jí)?

這就需要一個(gè)模型控制不同優(yōu)先級(jí)之間的關(guān)系與行為,于是lane模型誕生了。

  • lane模型通過(guò)將不同優(yōu)先級(jí)賦值給一個(gè)位,通過(guò)31位的位運(yùn)算來(lái)操作優(yōu)先級(jí)

如下是不同優(yōu)先級(jí)的定義:

  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. // 省略... 

上層實(shí)現(xiàn)

現(xiàn)在,我們可以說(shuō):

  • 從源碼層面講,Concurrent Mode是一套可控的“多優(yōu)先級(jí)更新架構(gòu)”。

那么基于該架構(gòu)之上可以實(shí)現(xiàn)哪些有意思的功能?我們舉幾個(gè)例子:

batchedUpdates

如果我們?cè)谝淮问录卣{(diào)中觸發(fā)多次更新,他們會(huì)被合并為一次更新進(jìn)行處理。

如下代碼執(zhí)行只會(huì)觸發(fā)一次更新:

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

這種合并多個(gè)更新的優(yōu)化方式被稱(chēng)為batchedUpdates。

batchedUpdates在很早的版本就存在了,不過(guò)之前的實(shí)現(xiàn)局限很多(脫離當(dāng)前上下文環(huán)境的更新不會(huì)被合并)。

在Concurrent Mode中,是以?xún)?yōu)先級(jí)為依據(jù)對(duì)更新進(jìn)行合并的,使用范圍更廣。

Suspense

Suspense[7]可以在組件請(qǐng)求數(shù)據(jù)時(shí)展示一個(gè)pending狀態(tài)。請(qǐng)求成功后渲染數(shù)據(jù)。

本質(zhì)上講Suspense內(nèi)的組件子樹(shù)比組件樹(shù)的其他部分擁有更低的優(yōu)先級(jí)。

useDeferredValue

useDeferredValue[8]返回一個(gè)延遲響應(yīng)的值,該值可能“延后”的最長(zhǎng)時(shí)間為timeoutMs。

例子:

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

在useDeferredValue內(nèi)部會(huì)調(diào)用useState并觸發(fā)一次更新。

這次更新的優(yōu)先級(jí)很低,所以當(dāng)前如果有正在進(jìn)行中的更新,不會(huì)受useDeferredValue產(chǎn)生的更新影響。所以u(píng)seDeferredValue能夠返回延遲的值。

當(dāng)超過(guò)timeoutMs后useDeferredValue產(chǎn)生的更新還沒(méi)進(jìn)行(由于優(yōu)先級(jí)太低一直被打斷),則會(huì)再觸發(fā)一次高優(yōu)先級(jí)更新。

總結(jié)

除了以上介紹的實(shí)現(xiàn),可以預(yù)見(jiàn),當(dāng)v17完美支持Concurrent Mode后,v18會(huì)迎來(lái)一大波基于Concurrent Mode的庫(kù)。

 

責(zé)任編輯:姜華 來(lái)源: 魔術(shù)師卡頌
相關(guān)推薦

2024-12-04 08:53:44

Docker腳本命令

2020-07-28 08:09:02

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

2021-11-17 07:44:29

React 前端 組件

2021-08-04 08:33:25

React服務(wù)端渲染

2022-05-27 20:53:42

數(shù)字化轉(zhuǎn)型數(shù)字化經(jīng)營(yíng)消費(fèi)者

2023-07-29 22:27:44

2019-10-14 13:20:26

物聯(lián)網(wǎng)數(shù)據(jù)IOT

2017-06-16 16:22:41

機(jī)房墻面

2017-10-25 09:50:51

Linux

2021-12-16 22:43:52

運(yùn)營(yíng)商手機(jī)號(hào)卡數(shù)據(jù)

2015-07-23 09:20:19

mmap

2018-01-08 14:18:14

代碼互聯(lián)網(wǎng)持續(xù)集成

2021-03-14 15:17:13

前端開(kāi)發(fā)架構(gòu)

2023-05-09 07:16:06

2018-02-07 00:00:00

數(shù)字化轉(zhuǎn)型

2018-08-02 15:24:05

RPCJava微服務(wù)

2022-07-14 07:17:11

LXCDocker語(yǔ)言

2018-07-18 15:02:54

混合云云戰(zhàn)略安全

2023-04-04 07:15:01

2018-07-09 14:44:27

存儲(chǔ)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 麻豆久久久久久久 | 中文字幕久久久 | 欧美在线视频一区 | 欧美一区二区三区在线播放 | 亚洲成av人片在线观看 | 中文字幕亚洲一区 | 欧美一区二区三区 | 一区二区三区四区毛片 | 亚洲精品九九 | 成人av网站在线观看 | 久久91精品久久久久久9鸭 | 国产一区二区在线免费观看 | 欧产日产国产精品视频 | 伊人网在线综合 | 成人精品久久久 | 欧美老妇交乱视频 | 久久国产欧美日韩精品 | 超碰在线免费 | 欧美电影一区 | 精品国产伦一区二区三区观看说明 | 在线视频一区二区三区 | 欧美成人a∨高清免费观看 色999日韩 | 国产成人网 | 日韩在线免费播放 | 亚洲韩国精品 | 国产精品欧美一区二区三区不卡 | 精品在线一区二区 | 日韩久久久一区二区 | 超碰在线久 | 欧美激情a∨在线视频播放 成人免费共享视频 | 97av在线| 亚洲第一成人影院 | 一区二区高清不卡 | 亚洲欧美网 | 欧美男人天堂 | 成人午夜视频在线观看 | 欧美精品一二三 | 亚洲精品久久久久久久久久吃药 | 久草在线| 中文字幕在线视频网站 | 亚洲中午字幕 |