自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于React的一切——React的并發(fā)悖論

開(kāi)發(fā) 前端
當(dāng)一個(gè)React應(yīng)用邏輯變得復(fù)雜后,「組件Render」花費(fèi)的時(shí)間會(huì)顯著增長(zhǎng)。如果從「組件Render」到「視圖渲染」期間消耗的時(shí)間過(guò)長(zhǎng),用戶就會(huì)感知到頁(yè)面卡頓。

大家好,我卡頌。

當(dāng)一個(gè)React應(yīng)用邏輯變得復(fù)雜后,「組件render」花費(fèi)的時(shí)間會(huì)顯著增長(zhǎng)。如果從「組件render」到「視圖渲染」期間消耗的時(shí)間過(guò)長(zhǎng),用戶就會(huì)感知到頁(yè)面卡頓。

為了解決這個(gè)問(wèn)題,有兩個(gè)方法:

  1. 讓「組件render」的過(guò)程從同步變?yōu)楫惒?,這樣render過(guò)程頁(yè)面不會(huì)卡死。這就是并發(fā)更新的原理。
  2. 減少需要render的組件數(shù)量,這就是常說(shuō)的React性能優(yōu)化。

通常,對(duì)于不同類(lèi)型組件,我們會(huì)采取以上不同的方法。比如,對(duì)于下面這樣的有耗時(shí)邏輯的輸入框,方法1更合適(因?yàn)椴l(fā)更新能減少輸入時(shí)的卡頓):

function ExpensiveInput({onChange, value}) {
// 耗時(shí)的操作
const cur = performance.now();
while (performance.now() - cur < 20) {}

return <input onChange={onChange} value={value}/>;
}

那么,能不能在整個(gè)應(yīng)用層面同時(shí)兼顧這2種方式呢?答案是 —— 不太行。

這是因?yàn)?,?duì)于復(fù)雜應(yīng)用,并發(fā)更新與性能優(yōu)化通常是相悖的。就是本文要聊的 —— 并發(fā)悖論。

從性能優(yōu)化聊起

對(duì)于一個(gè)組件,如果希望他非必要時(shí)不render,需要達(dá)到的基本條件是:props的引用不變。

比如,下面代碼中Child組件依賴fn props,由于fn是內(nèi)聯(lián)形式,所以每次App組件render時(shí)引用都會(huì)變,不利于Child性能優(yōu)化:

function App() {
return <Child fn={() => {/* xxx */}}/>
}

為了Child性能優(yōu)化,可以將fn抽離出來(lái):

const fn = () => {/* xxx */}

function App() {
return <Child fn={fn}/>
}

當(dāng)fn依賴某些props或者state時(shí),我們需要使用useCallback:

function App({a}) {
const fn = useCallback(() => a + 1, [a]);
return <Child fn={fn}/>
}

類(lèi)似的,其他類(lèi)型變量需要用到useMemo。

也就是說(shuō),當(dāng)涉及到性能優(yōu)化時(shí),React的代碼邏輯會(huì)變得復(fù)雜(需要考慮引用變化問(wèn)題)。

當(dāng)應(yīng)用進(jìn)一步復(fù)雜,會(huì)面臨更多問(wèn)題,比如:

  • 復(fù)雜的useEffect邏輯。
  • 狀態(tài)如何共享。

這些問(wèn)題會(huì)與性能優(yōu)化問(wèn)題互相疊加,最終導(dǎo)致應(yīng)用不僅邏輯復(fù)雜,性能也欠佳。

性能優(yōu)化的解決之道

好在,這些問(wèn)題有個(gè)共同的解決方法 —— 狀態(tài)管理。

上文我們聊到,對(duì)于性能優(yōu)化,關(guān)鍵的問(wèn)題是 —— 保持props引用不變。

在原生React中,如果a依賴b,b依賴c。那么,當(dāng)a變化后,我們需要通過(guò)各種方法(比如useCallback、useMemo)保持b、c引用的穩(wěn)定。

做這件事情本身(保持引用不變)對(duì)開(kāi)發(fā)者來(lái)說(shuō)就是額外的心智負(fù)擔(dān)。那么,狀態(tài)管理是如何解決這個(gè)問(wèn)題的呢?

答案是:狀態(tài)管理庫(kù)自己管理所有原始狀態(tài)以及派生狀態(tài)。

比如:

  • 在Recoil中,基礎(chǔ)狀態(tài)類(lèi)型被稱為Atom,其他派生狀態(tài)都是基于Atom組合而來(lái)
  • 在Zustand中,基礎(chǔ)狀態(tài)都是create方法創(chuàng)建的實(shí)例
  • 在Redux中,維護(hù)了一個(gè)全局狀態(tài),對(duì)于需要用到的狀態(tài)通過(guò)selector從中摘出來(lái)

這些狀態(tài)管理方案都會(huì)自己維護(hù)所有的基礎(chǔ)狀態(tài)與派生狀態(tài)。當(dāng)開(kāi)發(fā)者從狀態(tài)管理庫(kù)中引入狀態(tài)時(shí),就能最大限度保持props引用不變。

比如,下例用Zustand改造上面的代碼。由于狀態(tài)a和依賴a的fn都是由Zustand管理,所以fn的引用始終不變:

const useStore = create(set => ({
a: 0,
fn: () => set(state => ({ a: state.a + 1 })),
}))
function App() {
const fn = useStore(state => state.fn)
return <Child fn={fn}/>
}

并發(fā)更新的問(wèn)題

現(xiàn)在我們知道,性能優(yōu)化的通用解決途徑是 —— 通過(guò)狀態(tài)管理庫(kù),維護(hù)一套邏輯自洽的外部狀態(tài)(這里的「外部」是區(qū)別于React自身的狀態(tài)),保持引用不變。

但是,這套外部狀態(tài)最終一定會(huì)轉(zhuǎn)化為React的內(nèi)部狀態(tài)(再通過(guò)內(nèi)部狀態(tài)的變化驅(qū)動(dòng)視圖更新),所以就存在狀態(tài)同步時(shí)機(jī)的問(wèn)題。即:什么時(shí)候?qū)⑼獠繝顟B(tài)與內(nèi)部狀態(tài)同步?

在并發(fā)更新之前的React中,這并不是個(gè)問(wèn)題。因?yàn)楦率峭?、不?huì)被打斷的。所以對(duì)于同一個(gè)外部狀態(tài),在整個(gè)更新過(guò)程中都能保持不變。

比如,在如下代碼中,由于List?組件的render?過(guò)程不會(huì)打斷,所以list在遍歷過(guò)程中是穩(wěn)定的:

function List() {
const list = useStore(state => state.list)
return (
<ul>
{list.map(item => <Item key={item.id} data={item}/>}
</ul>
)
}

但是,對(duì)于開(kāi)啟并發(fā)更新的React,更新流程可能中斷,不同的Item組件可能是在中斷前后不同的宏任務(wù)中render,傳遞給他們的data props可能并不相同。這就導(dǎo)致同一次更新,同一個(gè)狀態(tài)(例子中的list)前后不一致的情況。

這種情況被稱為tearing(視圖撕裂)。

可以發(fā)現(xiàn),造成tearing的原因是 —— 外部狀態(tài)(狀態(tài)管理庫(kù)維護(hù)的狀態(tài))與React內(nèi)部狀態(tài)的同步時(shí)機(jī)出問(wèn)題。

這個(gè)問(wèn)題在當(dāng)前React中是很難解決的。退而求其次,為了讓這些狀態(tài)庫(kù)能夠正常使用,React專(zhuān)門(mén)出了個(gè)hook —— useSyncExternalStore。用于將狀態(tài)管理庫(kù)觸發(fā)的更新都以同步的方式執(zhí)行,這樣就不會(huì)有同步時(shí)機(jī)的問(wèn)題。

既然是以同步的方式執(zhí)行,那肯定沒(méi)法并發(fā)更新啦~~~

總結(jié)

實(shí)際上,凡是涉及到「自己維護(hù)了一個(gè)外部狀態(tài)」的庫(kù)(比如動(dòng)畫(huà)庫(kù)),都涉及到狀態(tài)同步的問(wèn)題,很有可能無(wú)法兼容并發(fā)更新。

所以,你會(huì)更傾向下面哪種選擇呢:

  1. 不care并發(fā)更新,以前React怎么用,現(xiàn)在就怎么用。
  2. 根據(jù)項(xiàng)目情況,平衡并發(fā)更新與性能優(yōu)化的訴求。
責(zé)任編輯:姜華 來(lái)源: 魔術(shù)師卡頌
相關(guān)推薦

2023-09-11 07:36:35

2020-09-11 10:55:10

useState組件前端

2021-02-28 09:47:54

軟件架構(gòu)軟件開(kāi)發(fā)軟件設(shè)計(jì)

2018-11-23 11:17:24

負(fù)載均衡分布式系統(tǒng)架構(gòu)

2021-02-19 23:08:27

軟件測(cè)試軟件開(kāi)發(fā)

2020-10-14 08:04:28

JavaScrip

2021-05-28 07:12:59

Python閉包函數(shù)

2022-08-21 17:35:31

原子多線程

2018-01-17 09:15:52

負(fù)載均衡算法

2023-04-12 14:04:48

光纖網(wǎng)絡(luò)

2022-08-17 06:25:19

偽共享多線程

2018-01-05 14:23:36

計(jì)算機(jī)負(fù)載均衡存儲(chǔ)

2021-08-09 14:40:02

物聯(lián)網(wǎng)IOT智能家居

2023-07-10 10:36:17

人工智能AI

2023-02-10 08:44:05

KafkaLinkedIn模式

2022-04-02 09:38:00

CSS3flex布局方式

2012-12-31 11:22:58

開(kāi)源開(kāi)放

2021-03-23 23:17:18

AI人工智能

2022-08-27 12:15:51

Linux Mint操作系統(tǒng)

2021-10-05 21:03:54

BeautifulSo 爬蟲(chóng)
點(diǎn)贊
收藏

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