React优化性能的经验教训
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
1. 基本原理
image.png
向更大的世界邁出第一步。
1.1 render()函數(shù)
一般來說,要盡可能少地在 render 函數(shù)中做操作。如果非要做一些復(fù)雜操作或者計(jì)算,也許你可以考慮使用一個(gè) memoized 函數(shù)以便于緩存那些重復(fù)的結(jié)果。可以看看 Lodash.memoize,這是一個(gè)開箱即用的記憶函數(shù)。
反過來講,避免在組件的 state 上存儲(chǔ)一些容易計(jì)算的值也很重要。舉個(gè)例子,如果 props 同時(shí)包含 firstName 和 lastName,沒必要在 state 上存一個(gè) fullName,因?yàn)樗梢院苋菀淄ㄟ^提供的 props 來獲取。如果一個(gè)值可以通過簡單的字符串拼接或基本的算數(shù)運(yùn)算從 props 派生出來,那么沒理由將這些值包含在組件的 state 上。
1.2 Prop 和 Reconciliation
重要的是要記住,只要 props(或 state)的值不等于之前的值,React 就會(huì)觸發(fā)重新渲染。如果 props 或者 state 包含一個(gè)對象或者數(shù)組,嵌套值中的改變會(huì)觸發(fā)重新渲染。考慮到這一點(diǎn),你需要注意在每次渲染的生命周期中,創(chuàng)建一個(gè)新的 props 或者 state 都可能無意中導(dǎo)致了性能下降。(注:對象或者數(shù)組只要引用不變,是不會(huì)觸發(fā)rerender的)
例子: 函數(shù)綁定的問題
/* 給 prop 傳入一個(gè)行內(nèi)綁定的函數(shù)(包括 ES6 箭頭函數(shù))實(shí)質(zhì)上是在每次父組件 render 時(shí)傳入一個(gè)新的函數(shù)。 */ render() {return (<div><a onClick={ () => this.doSomething() }>Bad</a><a onClick={ this.doSomething.bind( this ) }>Bad</a></div>); }/* 應(yīng)該在構(gòu)造函數(shù)中處理函數(shù)綁定并且將已經(jīng)綁定好的函數(shù)作為 prop 的值 */constructor( props ) {this.doSomething = this.doSomething.bind( this );//orthis.doSomething = (...args) => this.doSomething(...args); } render() {return (<div><a onClick={ this.doSomething }>Good</a></div>); }例子: 對象或數(shù)組字面量
/* 對象或者數(shù)組字面量在功能上來看是調(diào)用了 Object.create() 和 new Array()。這意味如果給 prop 傳遞了對象字面量或者數(shù)組字面量。每次render 時(shí) React 會(huì)將他們作為一個(gè)新的值。這在處理 Radium 或者行內(nèi)樣式時(shí)通常是有問題的。 *//* Bad */ // 每次渲染時(shí)都會(huì)為 style 新建一個(gè)對象字面量 render() {return <div style={ { backgroundColor: 'red' } }/> }/* Good */ // 在組件外聲明 const style = { backgroundColor: 'red' };render() {return <div style={ style }/> }例子 : 注意兜底值字面量
/* 有時(shí)我們會(huì)在 render 函數(shù)中創(chuàng)建一個(gè)兜底的值來避免 undefined 報(bào)錯(cuò)。在這些情況下,最好在組件外創(chuàng)建一個(gè)兜底的常量而不是創(chuàng)建一個(gè)新的字面量。 /* /* Bad */ render() {let thingys = [];// 如果 this.props.thingys 沒有被定義,一個(gè)新的數(shù)組字面量會(huì)被創(chuàng)建if( this.props.thingys ) {thingys = this.props.thingys;}return <ThingyHandler thingys={ thingys }/> }/* Bad */ render() {// 這在功能上和前一個(gè)例子一樣return <ThingyHandler thingys={ this.props.thingys || [] }/> }/* Good */// 在組件外部聲明 const NO_THINGYS = [];render() {return <ThingyHandler thingys={ this.props.thingys || NO_THINGYS }/> }1.3 盡可能的保持 Props(和 State)簡單和精簡
理想情況下,傳遞給組件的 props 應(yīng)該是它直接需要的。為了將值傳給子組件而將一個(gè)大的、復(fù)雜的對象或者很多獨(dú)立的 props 傳遞給一個(gè)組件會(huì)導(dǎo)致很多不必要的組件渲染(并且會(huì)增加開發(fā)復(fù)雜性)。
我們使用 Redux 作為狀態(tài)容器,所以在我們看來,最理想的是方案在組件層次結(jié)構(gòu)的每一個(gè)層級中使用 react-redux 的 connect() 函數(shù)直接從 store 上獲取數(shù)據(jù)。connect 函數(shù)的性能很好,并且使用它的開銷也非常小。
1.4 組件方法
由于組件方法是為組件的每個(gè)實(shí)例創(chuàng)建的,如果可能的話,使用 helper/util 模塊的純函數(shù)或者靜態(tài)類方法。尤其在渲染大量組件的應(yīng)用中會(huì)有明顯的區(qū)別。
2. 進(jìn)階
image.png
視圖的變化是邪惡的
2.1 shouldComponentUpdate()
React 有一個(gè)生命周期函數(shù) shouldComponentUpdate()。這個(gè)方法可以根據(jù)當(dāng)前的和下一次的 props 和 state 來通知這個(gè) React 組件是否應(yīng)該被重新渲染。
然而使用這個(gè)方法有一個(gè)問題,開發(fā)者必須考慮到需要觸發(fā)重新渲染的每一種情況。這會(huì)導(dǎo)致邏輯復(fù)雜,一般來說,會(huì)非常痛苦。如果非常需要,你可以使用一個(gè)自定義的shouldComponentUpdate()
方法,但是很多情況下有更好的選擇。
2.2 React.PureComponent
React 從 v15 開始會(huì)包含一個(gè) PureComponent 類,它可以被用來構(gòu)建組件。React.PureComponent聲明了它自己的 shouldComponentUpdate() 方法,它自動(dòng)對當(dāng)前的和下一次的 props 和 state 做一次淺對比。有關(guān)淺對比的更多信息,請參考這個(gè) Stack Overflow:http://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react
在大多數(shù)情況下,React.PureComponent 是比 React.Component更好的選擇。在創(chuàng)建新組件時(shí),首先嘗試將其構(gòu)建為純組件,只有組件的功能需要時(shí)才使用 React.Component。更多信息,請查閱相關(guān)文檔 React.PureComponent。
2.3 組件性能分析(在 Chrome 里)
在新版本的 Chrome 里,timeline 工具里有一個(gè)額外的內(nèi)置功能可以顯示哪些 React 組件正在渲染以及他們花費(fèi)的時(shí)間。要啟用此功能,將 ?react_perf
作為要測試的 URL 的查詢字符串。React 渲染時(shí)間軸數(shù)據(jù)將位于 User Timing 部分。
更多相關(guān)信息,請查閱官方文檔:Profiling Components with Chrome Timeline 。
2.4 有用的工具: why-did-you-update
這是一個(gè)很棒的 NPM 包,他們給 React 添加補(bǔ)丁,當(dāng)一個(gè)組件觸發(fā)了不必要的重新渲染時(shí),它會(huì)在控制臺(tái)輸出一個(gè) console 提示。
注意: 這個(gè)模塊在初始化時(shí)可以通過一個(gè)過濾器匹配特定的想要優(yōu)化的組件,否則你的命令行可能會(huì)被垃圾信息填滿,并且可能你的瀏覽器會(huì)掛起或者崩潰,查閱 why-did-you-update 文檔獲取更多詳細(xì)信息。
3. 常見性能陷阱
image.png
3.1 setTimeout() 和 setInterval()
在 React 組件中使用 setTimeout() 或者 setInterval() 要十分小心。幾乎總是有更好的選擇,例如 'resize' 和 'scroll' 事件(注意:有關(guān)注意事項(xiàng)請參閱下一節(jié))。
如果你需要使用 setTimeout() 和 setInterval(),你必須遵守下面兩條建議
不要設(shè)置過短的時(shí)間間隔。
當(dāng)心那些小于 100 ms 的定時(shí)器,他們很可能是沒意義的。如果確實(shí)需要一個(gè)更短的時(shí)間,可以使用 window.requestAnimationFrame()。
保留對這些函數(shù)的引用,并且在 unmount 時(shí)取消或者銷毀他們。
setTimeout() 和 setInterval() 都返回一個(gè)延遲函數(shù)的引用,并且需要的時(shí)候可以取消它們。由于這些函數(shù)是在全局作用域執(zhí)行的,他們不在乎你的組件是否存在,這會(huì)導(dǎo)致報(bào)錯(cuò)甚至程序卡死。
注意: 對 window.requestAnimationFrame() 來說也是如此
解決這個(gè)問題最簡答的方法是使用 react-timeout 這個(gè) NPM 包,它提供了一個(gè)可以自動(dòng)處理上述內(nèi)容的高階組件。它將 setTimeout/setInterval 等功能添加到包裝組建的 props 上。(特別感謝 Vixlet 的開發(fā)人員 Carl Pillot 提供這個(gè)方法)
如果你不想引入這個(gè)依賴,并且希望自行解決此問題,你可以使用以下的方法:
如果你使用 requestAnimationFrame() 執(zhí)行的一個(gè)動(dòng)畫循環(huán),可以使用一個(gè)非常相似的解決方案,當(dāng)前代碼要有一點(diǎn)小的修改:
// 如何確保我們的動(dòng)畫循環(huán)在組件消除時(shí)結(jié)束componentDidMount() {this.startLoop(); }componentWillUnmount() {this.stopLoop(); }startLoop() {if( !this._frameId ) {this._frameId = window.requestAnimationFrame( this.loop );} }loop() {// 在這里執(zhí)行循環(huán)工作this.theoreticalComponentAnimationFunction()// 設(shè)置循環(huán)的下一次迭代this.frameId = window.requestAnimationFrame( this.loop ) }stopLoop() {window.cancelAnimationFrame( this._frameId );// 注意: 不用擔(dān)心循環(huán)已經(jīng)被取消// cancelAnimationFrame() 不會(huì)拋出異常 }3.2 未去抖頻繁觸發(fā)的事件
某些常見的事件可能會(huì)非常頻繁的觸發(fā),例如 scroll,resize
。去抖這些事件是明智的,特別是如果事件處理程序執(zhí)行的不僅僅是基本功能。Lodash 有 _.debounce 方法。在 NPM 上還有一個(gè)獨(dú)立的 debounce 包.
“但是我真的需要立即反饋 scroll/resize 或者別的事件”
我發(fā)現(xiàn)一種可以處理這些事件并且以高性能的方式進(jìn)行響應(yīng)的方法,那就是在第一次事件觸發(fā)時(shí)啟動(dòng) requestAnimationFrame() 循環(huán)。然后可以使用 debounce() 方法并且將 trailing 這個(gè)配置項(xiàng)設(shè)為 true
(這意味著該功能只在頻繁觸發(fā)的事件流結(jié)束后觸發(fā))來取消對值的監(jiān)聽,看看下面這個(gè)例子。
3.3 密集CPU任務(wù)線程阻塞
某些任務(wù)一直是 CPU 密集型的,因此可能會(huì)導(dǎo)致主渲染線程的阻塞。舉幾個(gè)例子,比如非常復(fù)雜的數(shù)學(xué)計(jì)算,迭代非常大的數(shù)組,使用 File api 進(jìn)行文件讀寫,利用 <canvas> 對圖片進(jìn)行編碼解碼。
在這些情況下,如果有可能最好使用 Web Worker 將這些功能移到另一個(gè)線程上,這樣我們的主渲染線程可以保持順滑。
歡迎關(guān)注極客教程微信公眾號平臺(tái):geekjc
轉(zhuǎn)載于:https://my.oschina.net/cllgeek/blog/1584704
總結(jié)
以上是生活随笔為你收集整理的React优化性能的经验教训的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 刺激战场cdk改名卡购买
- 下一篇: 3DSMAX怎么制作欧式新古典客厅效果图