新聞中心
大家好,我卡頌。

創(chuàng)新互聯(lián)建站是一家朝氣蓬勃的網站建設公司。公司專注于為企業(yè)提供信息化建設解決方案。從事網站開發(fā),網站制作,網站設計,網站模板,微信公眾號開發(fā),軟件開發(fā),微信小程序開發(fā),10多年建站對成都自上料攪拌車等多個方面,擁有多年的網站推廣經驗。
當一個React應用邏輯變得復雜后,「組件render」花費的時間會顯著增長。如果從「組件render」到「視圖渲染」期間消耗的時間過長,用戶就會感知到頁面卡頓。
為了解決這個問題,有兩個方法:
- 讓「組件render」的過程從同步變?yōu)楫惒?,這樣render過程頁面不會卡死。這就是并發(fā)更新的原理。
- 減少需要render的組件數(shù)量,這就是常說的React性能優(yōu)化。
通常,對于不同類型組件,我們會采取以上不同的方法。比如,對于下面這樣的有耗時邏輯的輸入框,方法1更合適(因為并發(fā)更新能減少輸入時的卡頓):
function ExpensiveInput({onChange, value}) {
// 耗時的操作
const cur = performance.now();
while (performance.now() - cur < 20) {}
return ;
}那么,能不能在整個應用層面同時兼顧這2種方式呢?答案是 —— 不太行。
這是因為,對于復雜應用,并發(fā)更新與性能優(yōu)化通常是相悖的。就是本文要聊的 —— 并發(fā)悖論。
從性能優(yōu)化聊起
對于一個組件,如果希望他非必要時不render,需要達到的基本條件是:props的引用不變。
比如,下面代碼中Child組件依賴fn props,由于fn是內聯(lián)形式,所以每次App組件render時引用都會變,不利于Child性能優(yōu)化:
function App() {
return {/* xxx */}}/>
} 為了Child性能優(yōu)化,可以將fn抽離出來:
const fn = () => {/* xxx */}
function App() {
return
}當fn依賴某些props或者state時,我們需要使用useCallback:
function App({a}) {
const fn = useCallback(() => a + 1, [a]);
return
}類似的,其他類型變量需要用到useMemo。
也就是說,當涉及到性能優(yōu)化時,React的代碼邏輯會變得復雜(需要考慮引用變化問題)。
當應用進一步復雜,會面臨更多問題,比如:
- 復雜的useEffect邏輯。
- 狀態(tài)如何共享。
這些問題會與性能優(yōu)化問題互相疊加,最終導致應用不僅邏輯復雜,性能也欠佳。
性能優(yōu)化的解決之道
好在,這些問題有個共同的解決方法 —— 狀態(tài)管理。
上文我們聊到,對于性能優(yōu)化,關鍵的問題是 —— 保持props引用不變。
在原生React中,如果a依賴b,b依賴c。那么,當a變化后,我們需要通過各種方法(比如useCallback、useMemo)保持b、c引用的穩(wěn)定。
做這件事情本身(保持引用不變)對開發(fā)者來說就是額外的心智負擔。那么,狀態(tài)管理是如何解決這個問題的呢?
答案是:狀態(tài)管理庫自己管理所有原始狀態(tài)以及派生狀態(tài)。
比如:
- 在Recoil中,基礎狀態(tài)類型被稱為Atom,其他派生狀態(tài)都是基于Atom組合而來
- 在Zustand中,基礎狀態(tài)都是create方法創(chuàng)建的實例
- 在Redux中,維護了一個全局狀態(tài),對于需要用到的狀態(tài)通過selector從中摘出來
這些狀態(tài)管理方案都會自己維護所有的基礎狀態(tài)與派生狀態(tài)。當開發(fā)者從狀態(tài)管理庫中引入狀態(tài)時,就能最大限度保持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
}
并發(fā)更新的問題
現(xiàn)在我們知道,性能優(yōu)化的通用解決途徑是 —— 通過狀態(tài)管理庫,維護一套邏輯自洽的外部狀態(tài)(這里的「外部」是區(qū)別于React自身的狀態(tài)),保持引用不變。
但是,這套外部狀態(tài)最終一定會轉化為React的內部狀態(tài)(再通過內部狀態(tài)的變化驅動視圖更新),所以就存在狀態(tài)同步時機的問題。即:什么時候將外部狀態(tài)與內部狀態(tài)同步?
在并發(fā)更新之前的React中,這并不是個問題。因為更新是同步、不會被打斷的。所以對于同一個外部狀態(tài),在整個更新過程中都能保持不變。
比如,在如下代碼中,由于List?組件的render?過程不會打斷,所以list在遍歷過程中是穩(wěn)定的:
function List() {
const list = useStore(state => state.list)
return (
- }
{list.map(item =>
)
}
但是,對于開啟并發(fā)更新的React,更新流程可能中斷,不同的Item組件可能是在中斷前后不同的宏任務中render,傳遞給他們的data props可能并不相同。這就導致同一次更新,同一個狀態(tài)(例子中的list)前后不一致的情況。
這種情況被稱為tearing(視圖撕裂)。
可以發(fā)現(xiàn),造成tearing的原因是 —— 外部狀態(tài)(狀態(tài)管理庫維護的狀態(tài))與React內部狀態(tài)的同步時機出問題。
這個問題在當前React中是很難解決的。退而求其次,為了讓這些狀態(tài)庫能夠正常使用,React專門出了個hook —— useSyncExternalStore。用于將狀態(tài)管理庫觸發(fā)的更新都以同步的方式執(zhí)行,這樣就不會有同步時機的問題。
既然是以同步的方式執(zhí)行,那肯定沒法并發(fā)更新啦~~~
總結
實際上,凡是涉及到「自己維護了一個外部狀態(tài)」的庫(比如動畫庫),都涉及到狀態(tài)同步的問題,很有可能無法兼容并發(fā)更新。
所以,你會更傾向下面哪種選擇呢:
- 不care并發(fā)更新,以前React怎么用,現(xiàn)在就怎么用。
- 根據(jù)項目情況,平衡并發(fā)更新與性能優(yōu)化的訴求。
分享題目:關于React的一切——React的并發(fā)悖論
當前URL:http://www.dlmjj.cn/article/dpdjcsh.html


咨詢
建站咨詢
