摘要:出現(xiàn)紅幀表示頁(yè)面已經(jīng)超負(fù)荷,會(huì)出現(xiàn)卡頓,響應(yīng)緩慢等現(xiàn)象。因此當(dāng)滑動(dòng)周日歷時(shí)已經(jīng)不會(huì)有紅幀發(fā)生了。我的目的是每一次遞歸會(huì)調(diào)用一次與但是這樣寫(xiě)只會(huì)在遞歸結(jié)束時(shí)調(diào)用一次因此修改如下這樣優(yōu)化之后,發(fā)現(xiàn)內(nèi)存占用下降一些,但是紅幀仍然存在。
性能優(yōu)化可以說(shuō)是衡量一個(gè)前端程序員react使用水平的重要標(biāo)準(zhǔn)。
在學(xué)習(xí)react之初的時(shí)候,由于對(duì)react不夠了解,寫(xiě)的項(xiàng)目雖然功能都實(shí)現(xiàn)了,但是性能優(yōu)化方面的考慮卻做得很少,因此回過(guò)頭來(lái)發(fā)現(xiàn)自己以前寫(xiě)的react代碼確實(shí)有點(diǎn)糟糕。
為了提高自己的react水平,閑暇之余就把以前的老項(xiàng)目拿出來(lái)分析優(yōu)化,看看都有哪些問(wèn)題。這里就以我以前做過(guò)的一個(gè)《投資日歷》為例做一次優(yōu)化記錄。
項(xiàng)目線上地址:https://www.itiger.com/activi...
優(yōu)化工具timeline/performance基礎(chǔ)使用教程:
https://developers.google.com...
chrome在版本57還是58的時(shí)候,將Timeline更名為performance
該項(xiàng)目主要的難點(diǎn)與性能瓶頸在于日歷的左右滑動(dòng)與切換。由于需求定制程度非常高,沒(méi)有合適的第三方日歷插件,所以就自己實(shí)現(xiàn)了一個(gè)。支持周日歷與月日歷的切換,支持左右滑動(dòng)切換日期。
滑動(dòng)效果僅支持移動(dòng)端
問(wèn)題出現(xiàn)在公司一款老的android測(cè)試機(jī),發(fā)現(xiàn)動(dòng)畫(huà)效果非??D。因此有了優(yōu)化的必要。
首先利用performance工具的的錄制功能錄制一段操作過(guò)程。
點(diǎn)擊左上角的黑色原點(diǎn)開(kāi)始錄制。錄制過(guò)程中,多次滑動(dòng)周日歷即可。然后大約5~10秒點(diǎn)擊stop按鈕停止錄制。
錄制結(jié)果如圖。
從上圖中我們可以發(fā)現(xiàn)以下問(wèn)題:
1、 窗格中出現(xiàn)了紅幀。出現(xiàn)紅幀表示頁(yè)面已經(jīng)超負(fù)荷,會(huì)出現(xiàn)卡頓,響應(yīng)緩慢等現(xiàn)象。
2、 大量的黃色區(qū)域,黃色區(qū)域越大,表示JavaScript的運(yùn)行過(guò)程中的壓力也越大。
3、 高額的內(nèi)存占用,以及不正常的波動(dòng)曲線(藍(lán)色)。詳細(xì)信息可以在上圖中的JS Heap中查看。26.6 ~ 71.6M。
我們可以在Main中觀察到當(dāng)前時(shí)刻的函數(shù)調(diào)用棧詳情。當(dāng)出現(xiàn)紅幀,選中紅幀區(qū)域,Main區(qū)域發(fā)現(xiàn)變化,變?yōu)楫?dāng)前選擇時(shí)段的函數(shù)調(diào)用棧詳情。我們會(huì)發(fā)現(xiàn)函數(shù)調(diào)用棧最上層有一個(gè)紅色三角形。點(diǎn)擊會(huì)在下面的Summary里發(fā)現(xiàn)對(duì)應(yīng)的信息以及警告。如下圖中的Warning: Recuring handler took 86.69 ms。
4、 層級(jí)很高的函數(shù)調(diào)用棧。查看紅色區(qū)域的函數(shù)調(diào)用棧,我們會(huì)發(fā)現(xiàn)大量的react組件方法被重復(fù)調(diào)用。
從上面的分析就可以簡(jiǎn)單看出,雖然實(shí)現(xiàn)了非常復(fù)雜的功能,看上去很厲害的樣子,其實(shí)內(nèi)部非常糟糕。幾乎可以作為react用法的反面教材了。
優(yōu)化分析1
在上面的函數(shù)調(diào)用棧中,我們發(fā)現(xiàn)有一個(gè)方法出現(xiàn)的次數(shù)非常多,那就是receiveComponent。因此可以預(yù)想到某個(gè)組件里肯定使用了receiveComponent相關(guān)的生命周期的方法。檢查代碼,確實(shí)發(fā)現(xiàn)了幾處componentWillReceiveProps的使用。
// 每一次更新?tīng)顟B(tài)都會(huì)刷新一次,導(dǎo)致了大量的計(jì)算 componentWillReceiveProps(nextProps) { this.setState({ navProcess: getNavigation(nextProps.currentData) }) }
剛開(kāi)始學(xué)習(xí)react時(shí)可能會(huì)認(rèn)為生命周期是一個(gè)學(xué)習(xí)難點(diǎn),我們不知道什么情況下去使用它們。慢慢的隨著經(jīng)驗(yàn)的增加,才發(fā)現(xiàn),生命周期方法是萬(wàn)萬(wàn)不能輕易使用的。特別是與props/state改變,與組件重新渲染相關(guān)的幾個(gè)生命周期,如componentWillReceiveProps, shouldComponentUpdate ,componentWillUpdate等。這個(gè)實(shí)際案例告訴我們,他們的使用,會(huì)造成高額的性能消耗。所以不到萬(wàn)不得已,不要輕易使用他們。
曾經(jīng)看到過(guò)一篇英文博文,分析的是寧愿多幾次render,也不要使用shouldComponentUpdate來(lái)優(yōu)化代碼。但是文章地址找不到,如果有其他看過(guò)的朋友請(qǐng)?jiān)谠u(píng)論里留言分享一下,感謝
而只有componentDidMount是非常常用的。
上面幾行簡(jiǎn)單的代碼,暴露了一個(gè)非??植赖膯?wèn)題。一個(gè)是使用了生命周期componentWillReceiveProps。而另一個(gè)則是在props改變的同時(shí),還修改了組件的state。我們知道當(dāng)props在父級(jí)被改變時(shí)會(huì)造成組件的重新渲染,而組件內(nèi)部的state的改變同樣也會(huì)造成組件的重新渲染,因此這幾句簡(jiǎn)單的代碼,讓組件發(fā)生了很多次冗余的渲染。
因此優(yōu)化的方向就朝這兩個(gè)方向努力。首先不能使用componentWillReceiveProps,其次我發(fā)現(xiàn)navProcess其實(shí)可以在父級(jí)組件中計(jì)算,并通過(guò)props傳遞下來(lái)。所以?xún)?yōu)化后的代碼如下:
function Index(props) { const { currentD, currentM, selectD, setDate, loading, error, process, navProcess } = props; return () } export default withWrapped(Index);{ loading ? null : error ? : } {loading ? : null}
意外的驚喜是發(fā)現(xiàn)該組件最終優(yōu)化成為了一個(gè)無(wú)狀態(tài)組件,輕裝上陣,完美。
這樣優(yōu)化之后,重新渲染的發(fā)生少了好幾倍,運(yùn)行壓力自然減少很多。因此當(dāng)滑動(dòng)周日歷時(shí)已經(jīng)不會(huì)有紅幀發(fā)生了。但是月日歷由于DOM節(jié)點(diǎn)更多,仍然存在問(wèn)題,因此核心的問(wèn)題還不在這里。我們還得繼續(xù)觀察。
優(yōu)化分析2
在函數(shù)調(diào)用棧中我們可以很明顯的看到一個(gè)名為ani的方法。而這個(gè)方法是我自己寫(xiě)的運(yùn)動(dòng)實(shí)現(xiàn)。因此我得重點(diǎn)關(guān)注它的實(shí)現(xiàn)中是不是存在什么問(wèn)題。仔細(xì)瀏覽一遍,果然有問(wèn)題。
發(fā)現(xiàn)在ani方法的回調(diào)中,調(diào)用了2次setDate方法。
// 導(dǎo)致頂層高階組件多一次渲染,下層多很多次渲染 setDate(newCur, 0); setDate({ year: newCur.year, month: newCur.month }, 1)
該setDate方法是在父級(jí)中定義用來(lái)修改父級(jí)state的方法。他的每一次調(diào)用都會(huì)引發(fā)由上自下的重新渲染,因此多次調(diào)用的代價(jià)是非常大的。所以我將要面臨的優(yōu)化就是想辦法將這兩次調(diào)用合并為一次。
先看看優(yōu)化以前setDate方法的定義是如何實(shí)現(xiàn)的。我想要通過(guò)不同的number來(lái)修改不同的state屬性。但是沒(méi)有考慮如果需要修改多個(gè)呢?
setDate = (date, number) => { if (number == 0) { this.setState({ currentD: date, currentM: { year: date.year, month: date.month } }) } if (number == 1) { this.setState({ currentM: date }) } if (number == 2) { _date = date; _month = { year: date.year, month: date.month }; this.setState({ currentD: _date, currentM: _month, selectD: _date }) this.process(date); } }
修改該方法為,傳遞一個(gè)對(duì)象字面量進(jìn)去進(jìn)行修改
setDate = (options) => { const state = { ...this.state, ...options }; if (options.selectD) { _date = options.selectD; _month = { year: _date.year, month: _date.month } state.currentD = _date; state.currentM = _month; this.process(_date, state); } else { this.setState(state); } }
該方法有兩處優(yōu)化,第一處優(yōu)化是傳入的參數(shù)調(diào)整,想要修改那一個(gè)就直接傳入,用法類(lèi)似setState。第二處優(yōu)化是在this.process方法中只調(diào)用一次this.setState,總之這樣處理的目的都是統(tǒng)一的,當(dāng)想要數(shù)據(jù)修改時(shí)只發(fā)生一次渲染。而之前的方法會(huì)導(dǎo)致3次甚至多次渲染。這樣優(yōu)化之后,性能自然會(huì)提升很多。
優(yōu)化分析3
但是優(yōu)化并沒(méi)有結(jié)束,因?yàn)樵黉浿埔欢尾榭?,仍然?huì)發(fā)現(xiàn)紅幀出現(xiàn)。
進(jìn)一步查看Calendar組件,發(fā)現(xiàn)每一次滑動(dòng)切換,都會(huì)發(fā)生4次渲染??隙ㄓ袉?wèn)題。
我的目的是最多發(fā)生兩次無(wú)法避免的渲染。多余的肯定是因?yàn)榇a的問(wèn)題導(dǎo)致的冗余渲染。因此繼續(xù)查看代碼。
發(fā)現(xiàn)在遞歸調(diào)用ani方法時(shí),this.timer并沒(méi)有被及時(shí)取消。
// 我的目的是每一次遞歸會(huì)調(diào)用一次requestAnimationFrame與cancelAnimationFrame // 但是這樣寫(xiě)只會(huì)在遞歸結(jié)束時(shí)調(diào)用一次cancelAnimationFrame if (offset == duration) { callback && callback(); cancelAnimationFrame(this.timer); } else { this.timer = requestAnimationFrame(ani); }
因此修改如下:
ani = () => { .... if (offset == duration) { callback && callback(); } else { this.timer = requestAnimationFrame(ani); } cancelAnimationFrame(this.timer); }
這樣優(yōu)化之后,發(fā)現(xiàn)內(nèi)存占用下降一些,但是紅幀仍然存在。看來(lái)計(jì)算量并沒(méi)有下降。繼續(xù)優(yōu)化。
優(yōu)化分析4
發(fā)現(xiàn)Calendar組件中,根據(jù)props中的curDate,curMonth計(jì)算而來(lái)的weekInfo與monthInfo被寫(xiě)在了該組件的state中。由于state中數(shù)據(jù)的變化都會(huì)導(dǎo)致重新渲染,而我發(fā)現(xiàn)在代碼中有多處對(duì)他們進(jìn)行修改。
componentDidMount() { const { curDate, curMonth } = this.props this.setState({ weekInfo: calendar.get3WeekInfo(curDate), monthInfo: calendar.get3MonthInfo(curMonth) }) this.setMessageType(curDate, 0); this.setMessageType(curMonth, 1); }
其實(shí)這種根據(jù)props中的參數(shù)計(jì)算而來(lái)的數(shù)據(jù)是萬(wàn)萬(wàn)不能寫(xiě)在state中的,因?yàn)閜rops數(shù)據(jù)的變化也會(huì)導(dǎo)致組件刷新重新渲染,因此一個(gè)數(shù)據(jù)變化就會(huì)導(dǎo)致不可控制的多次渲染。這個(gè)時(shí)候更好的方式是直接在render中計(jì)算。因此優(yōu)化如下:
render() { ... let info = type == 0 ? c.get3WeekInfo(curDate) : c.get3MonthInfo(curMonth); ... }
優(yōu)化結(jié)果如下圖
與第一張圖對(duì)比,我們發(fā)現(xiàn),運(yùn)動(dòng)過(guò)程中出現(xiàn)的紅幀沒(méi)有了。二是窗格中黃色區(qū)域大量減少,表示js的計(jì)算量減少很多。三是內(nèi)存占用大幅降低,從最高的71M減少到了33M。內(nèi)存的增長(zhǎng)也更加平滑。
后續(xù)的優(yōu)化大致目的都是一樣。不再贅述。
最后總結(jié)一下:
盡量避免生命周期方法的使用,特別是與狀態(tài)更新相關(guān)的生命周期,使用時(shí)一定要慎重。
能通過(guò)props重新渲染組件,就不要在額外添加state來(lái)增加渲染壓力。
一切的優(yōu)化方向就是在實(shí)現(xiàn)功能的前提下減少重新渲染的發(fā)生。
這其中涉及到的技巧就需要大家在實(shí)戰(zhàn)中慢慢掌握了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92094.html
摘要:前端日?qǐng)?bào)精選精讀與提案知乎專(zhuān)欄第期認(rèn)識(shí)引擎記錄一次利用工具進(jìn)行性能優(yōu)化的真實(shí)案例簡(jiǎn)書(shū)中的使用規(guī)則教程繼承的實(shí)現(xiàn)方法個(gè)人文章中文譯組件渲染性能探索個(gè)人文章周刊第期表單性能的改進(jìn)實(shí)踐知乎專(zhuān)欄簡(jiǎn)單可重用的圖表庫(kù)知乎專(zhuān)欄 2017-07-08 前端日?qǐng)?bào) 精選 精讀 TC39 與 ECMAScript 提案 - 知乎專(zhuān)欄【第989期】認(rèn)識(shí) V8 引擎記錄一次利用 Timeline/Perform...
摘要:?jiǎn)?dòng)性能瓶頸分析與解決方案翻譯自的,從屬于筆者的前端入門(mén)與工程實(shí)踐。我們必須要清醒地認(rèn)識(shí)到全面評(píng)測(cè)以挖掘出真正性能瓶頸的重要性。這可能是最佳的方式了,類(lèi)似于這樣的模式鼓勵(lì)基于路由的分組,目前被與廣泛使用。 JavaScript 啟動(dòng)性能瓶頸分析與解決方案 翻譯自 Addy Osmani 的 JavaScript Start-up Performance,從屬于筆者的Web 前端入門(mén)與工...
摘要:在此,我們可以使用懶加載方式對(duì)其進(jìn)行優(yōu)化,僅展示其對(duì)應(yīng)類(lèi)型的圖,避免了不必要的資源浪費(fèi)和計(jì)算時(shí)間。 這篇文章將介紹下實(shí)際使用performance對(duì)頁(yè)面進(jìn)行優(yōu)化的過(guò)程??偟膩?lái)說(shuō),chrome performance工具讓我們更方便的發(fā)現(xiàn)在代碼運(yùn)行過(guò)程中的問(wèn)題在哪里,便于對(duì)一些可能注意不到的問(wèn)題進(jìn)行定位、分析和優(yōu)化。原文首發(fā)于個(gè)人博客 渲染優(yōu)化 首先,我們對(duì)進(jìn)入整個(gè)詳情頁(yè)進(jìn)行分析,整個(gè)頁(yè)...
摘要:性能時(shí)間線以一個(gè)統(tǒng)一的接口獲取由和所收集的性能數(shù)據(jù)。瀏覽器支持下表列舉了當(dāng)前主流瀏覽器對(duì)性能的支持,其中標(biāo)注星號(hào)的內(nèi)容并非來(lái)自于性能工作小組。 頁(yè)面的性能問(wèn)題一直是產(chǎn)品開(kāi)發(fā)過(guò)程中的重要一環(huán),很多公司也一直在使用各種方式監(jiān)控產(chǎn)品的頁(yè)面性能。從控制臺(tái)工具、Fiddler抓包工具,到使用DOMContentLoaded和document.onreadystatechange這種侵入式j(luò)ava...
摘要:接下來(lái)看下偽代碼調(diào)度算法偽代碼原來(lái)這段寫(xiě)的匆忙且不好,重新更新了一篇講調(diào)度算法的大概實(shí)現(xiàn)性能改善的原理二。 問(wèn)題背景 React16 更新了底層架構(gòu),新架構(gòu)主要解決更新節(jié)點(diǎn)過(guò)多時(shí),頁(yè)碼卡頓的問(wèn)題。譬如如下代碼,根據(jù)用戶(hù)輸入的文字生成10000行數(shù)據(jù),用戶(hù)輸入框會(huì)出現(xiàn)卡頓現(xiàn)象。 class App extends React.Component { constructor( prop...
閱讀 2795·2023-04-26 01:47
閱讀 3601·2023-04-25 23:45
閱讀 2481·2021-10-13 09:39
閱讀 617·2021-10-09 09:44
閱讀 1807·2021-09-22 15:59
閱讀 2786·2021-09-13 10:33
閱讀 1733·2021-09-03 10:30
閱讀 667·2019-08-30 15:53