前言
對于前端開發(fā)攻城獅們來說,性能優(yōu)化是一個永恒的話題。隨著前端需求復雜度的不斷升高,在項目中想始終保持著良好的性能也逐漸成為了一個有挑戰(zhàn)的事情。本次分享簡述我們在 Rax 項目中常用的一些性能優(yōu)化方式,并將從近期的一個實際業(yè)務需求出發(fā),講述我在 Rax C端應用場景下所遇到性能問題排查時的心路歷程。
本次分享的優(yōu)化內(nèi)容,主要是指用戶滑動屏幕瀏覽頁面過程中所花費的單幀渲染耗時的降低比例。 單幀渲染耗時通過 chrome dev tools 插件以及手淘端app dev tools測得。一般來說瀏覽器運行幀率為 60hz,因此對于幀率要求非常高的場景如內(nèi)容輸入后的響應、動畫等,需要盡可能的將單幀耗時控制在 16.6ms(1s / 60)以內(nèi),用戶才能完全感知不到系統(tǒng)的卡頓。對于一般性能要求沒那么高的應用場景,單幀耗時能達到33.2ms(2s / 60),也能達到一個相對流暢的用戶體驗。
實際業(yè)務現(xiàn)狀
接下來我將從近期參與的一個實際業(yè)務需求出發(fā),記錄下我在這個項目中遇到性能問題時,發(fā)現(xiàn)問題、解決問題的心路歷程。
從實際業(yè)務場景出發(fā),我們發(fā)現(xiàn)不少頁面在用戶滑動瀏覽頁面的過程中存在卡頓,一些情況下卡頓嚴重,非常影響用戶體驗,由于多數(shù)安卓機的渲染性能不如IOS系統(tǒng),安卓端的性能問題會尤其明顯
那么如何優(yōu)化這個性能問題?
在 Rax當前的版本中,rax框架使用同步渲染模式(Synchronous Rendering),也就是說,在處理更新時會阻塞整個 UI 線程,直到所有更新完成才會重新渲染視圖。這種方式雖然簡單易懂,但是在處理大量或復雜數(shù)據(jù)時存在明顯的性能問題,導致應用程序出現(xiàn)卡頓或者無響應等問題。
React18的并發(fā)模式(Concurrent Mode),它可以將渲染任務分解成多個小塊,并通過 React 的調(diào)度器(Scheduler)來優(yōu)化執(zhí)行順序和時間,從而降低了單次渲染所需的計算資源和時間。并發(fā)模式可以在渲染過程中及時響應用戶的輸入和請求,避免出現(xiàn) UI 卡頓或者無響應的情況,從而提高了整個應用的用戶體驗。
目前Rax框架在性能優(yōu)化上雖然不及react18,但仍然有非常好的應用渲染性能基礎,站在了巨人的肩膀上,我們所能做的,便是在不添亂的前提下,幫助Rax更快、更高效的完成遍歷渲染的過程,使更新鏈路盡可能的短的走完。
排查流程
通過安卓手淘掃碼觀察app dev tools的性能分析,每幀普遍在 700ms以上,最嚴重的長達 1847ms
觀察發(fā)現(xiàn)最耗時的任務為一個叫appear的函數(shù)執(zhí)行,appear函數(shù)出現(xiàn)在trackerLink組件中,是曝光方法,也就是說,每當一個標簽需要進行曝光,那么當這個元素出現(xiàn)在瀏覽器視口,就會觸發(fā)此appear方法,隨著一次又一次的需求迭代,需要曝光的點越來越多,導致每一次元素重新出現(xiàn)在適口,都會有大量的appear方法調(diào)用,消耗了大量的性能,在安卓系統(tǒng)性能不夠優(yōu)秀的機型上,用戶體驗表現(xiàn)就會卡頓。
當我們滑動頁面時,由于大量元素需要進行曝光操作,appear函數(shù)長時間執(zhí)行,對于用戶來講,這段時間界面是卡死且無法交互的。
如果我們把這個例子中的appear函數(shù)類比成Rax的更新過程:即setState觸發(fā)了一次更新,而這次更新耗時非常久,比如1800ms。那么在這1800ms的時間內(nèi)界面是卡死的,用戶無法進行交互,非常影響用戶的使用體驗。
優(yōu)化思路
雪中送炭
擒賊先擒王,首先解決最影響性能的,怎么讓appear方法的執(zhí)行不影響主線程的渲染?
1.接入react18?
成本太高。pass
2.使用requestIdleCallback來控制appear方法的執(zhí)行不影響主線程?
requestIdleCallback 會將任務安排在瀏覽器的空閑時期執(zhí)行。在當前頁面的生命周期內(nèi),也不能保證 requestIdleCallback 中的任務一定會執(zhí)行。如果瀏覽器長時間忙于處理高優(yōu)先級任務,低優(yōu)先級的空閑回調(diào)可能會被推遲,直到有足夠的空閑時間來執(zhí)行它們。如果這種情況一直持續(xù),那么這些任務可能會被無限期地推遲。pass
3.如果任務是關(guān)鍵性的,需要在特定時間內(nèi)一定要執(zhí)行,那么使用 setTimeout可能是更好的選擇。setTimeout這個API提供了更可靠的方式來計劃任務的執(zhí)行,即使它們沒有 requestIdleCallback 提供的執(zhí)行時機那樣靈活。
setTimeout將同步任務變?yōu)楫惒饺蝿?,當需要trackertLink組件需要曝光時,將曝光函數(shù)異步執(zhí)行,不阻塞影響主線程渲染,提高用戶體驗性。
改動TrackerLink組件:
// needAsyncSending控制埋點曝光異步進行,不阻塞主線程
if (needAsyncSending) {
const sendExpTimer = setTimeout(() => {
recordExp();
clearTimeout(sendExpTimer);
}, 0)
} else {
recordExp();
}
優(yōu)化后:
我們可以看到,當trakcerLink組件異步調(diào)用曝光方法時,每幀渲染時間大幅縮減,效果明顯,每幀最糟糕的情況從1800ms降低至每幀最糟糕的情況200ms左右,
錦上添花
通過異步發(fā)送曝光埋點解決了最大頭的性能問題,用戶滑動頁面時已經(jīng)不會有明顯的延時和滑不動,但是任然有部分頓挫感,由于無法使用react app dev tools工具觀察組件的渲染情況,我們使用console.log來判斷哪些組件一直在重新渲染,發(fā)現(xiàn)在滾動過程中,航班報價列表和報價卡片一直在重新渲染,
但是業(yè)務場景下,此種渲染是不必要的,當沒有重新發(fā)起請求時,是不需要一直重新渲染ota報價卡片
避免 State 的不必要更新
一些業(yè)務場景下,不需要一直更新setState,找到不必要的setState,避免這些更新
防止父組件重新渲染重新聲明方法導致子組件不必要的重新渲染
純函數(shù)組件使用 Memo 包裹,純函數(shù)組件的function類型的prop使用useCallback包裹
?===========>>>>>
最終優(yōu)化效果:
可以看到最終性能分析得到的數(shù)據(jù)沒有明顯卡頓的地方,很多幀也達到了16.7ms的理想狀態(tài)文章來源:http://www.zghlxwxcb.cn/news/detail-831821.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-831821.html
到了這里,關(guān)于記一次rax應用用戶體驗性能優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!