React并發(fā)更新與性能優(yōu)化解析
正文
當(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è)方法:
- 讓組件render的過(guò)程從同步變?yōu)楫惒?,這樣
render
過(guò)程頁(yè)面不會(huì)卡死。這就是并發(fā)更新的原理 - 減少需要
render
的組件數(shù)量,這就是常說(shuō)的React
性能優(yōu)化
通常,對(duì)于不同類型組件,我們會(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}/> }
類似的,其他類型變量需要用到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ì)開發(fā)者來(lái)說(shuō)就是額外的心智負(fù)擔(dān)。那么,狀態(tài)管理是如何解決這個(gè)問(wèn)題的呢?
答案是:狀態(tài)管理庫(kù)自己管理所有原始狀態(tài)以及派生狀態(tài)。
比如:
- 在
Recoil
中,基礎(chǔ)狀態(tà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)開發(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ì)于開啟并發(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
專門出了個(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)畫庫(kù)),都涉及到狀態(tài)同步的問(wèn)題,很有可能無(wú)法兼容并發(fā)更新。
所以,你會(huì)更傾向下面哪種選擇呢:
- 不
care
并發(fā)更新,以前React
怎么用,現(xiàn)在就怎么用 - 根據(jù)項(xiàng)目情況,平衡并發(fā)更新與性能優(yōu)化的訴求
以上就是React并發(fā)更新與性能優(yōu)化解析的詳細(xì)內(nèi)容,更多關(guān)于React并發(fā)更新性能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ForwardRef?useImperativeHandle方法demo
這篇文章主要為大家介紹了ForwardRef?useImperativeHandle方法demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03react中useEffect函數(shù)的詳細(xì)用法(最新推薦)
useEffect是React中的一個(gè)Hook,用于在函數(shù)組件中處理副作用(如數(shù)據(jù)獲取、訂閱、手動(dòng)更改 DOM 等),useEffect屬于組件的生命周期方法,下面通過(guò)本文給大家分享react中useEffect函數(shù)的詳細(xì)用法,感興趣的朋友跟隨小編一起看看吧2024-06-06React Native如何消除啟動(dòng)時(shí)白屏的方法
本篇文章主要介紹了React Native如何消除啟動(dòng)時(shí)白屏的方法,詳細(xì)的介紹了解決的方法,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08使用React+ts實(shí)現(xiàn)無(wú)縫滾動(dòng)的走馬燈詳細(xì)過(guò)程
這篇文章主要給大家介紹了關(guān)于使用React+ts實(shí)現(xiàn)無(wú)縫滾動(dòng)的走馬燈詳細(xì)過(guò)程,文中給出了詳細(xì)的代碼示例以及圖文教程,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-08-08說(shuō)說(shuō)react中引入css的方式有哪些并區(qū)別在哪
本文主要介紹了說(shuō)說(shuō)react中引入css的方式有哪些并區(qū)別在哪,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04React+Node實(shí)現(xiàn)大文件分片上傳、斷點(diǎn)續(xù)傳秒傳思路
本文主要介紹了React+Node實(shí)現(xiàn)大文件分片上傳、斷點(diǎn)續(xù)傳秒傳思路,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02React Refs轉(zhuǎn)發(fā)實(shí)現(xiàn)流程詳解
Refs是一個(gè) 獲取 DOM節(jié)點(diǎn)或React元素實(shí)例的工具,在React中Refs 提供了一種方式,允許用戶訪問(wèn)DOM 節(jié)點(diǎn)或者在render方法中創(chuàng)建的React元素,這篇文章主要給大家介紹了關(guān)于React中refs的一些常見用法,需要的朋友可以參考下2022-12-12react+antd4實(shí)現(xiàn)優(yōu)化大批量接口請(qǐng)求
這篇文章主要為大家詳細(xì)介紹了如何使用react hooks + antd4實(shí)現(xiàn)大批量接口請(qǐng)求的前端優(yōu)化,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-02-02react組件中過(guò)渡動(dòng)畫的問(wèn)題解決
這篇文章主要為大家介紹了react組件中過(guò)渡動(dòng)畫的問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09