日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

React优化性能的经验教训

發(fā)布時間:2023/12/15 编程问答 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React优化性能的经验教训 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

1. 基本原理


image.png

向更大的世界邁出第一步。

1.1 render()函數(shù)

一般來說,要盡可能少地在 render 函數(shù)中做操作。如果非要做一些復(fù)雜操作或者計算,也許你可以考慮使用一個 memoized 函數(shù)以便于緩存那些重復(fù)的結(jié)果??梢钥纯?Lodash.memoize,這是一個開箱即用的記憶函數(shù)。

反過來講,避免在組件的 state 上存儲一些容易計算的值也很重要。舉個例子,如果 props 同時包含 firstName 和 lastName,沒必要在 state 上存一個 fullName,因為它可以很容易通過提供的 props 來獲取。如果一個值可以通過簡單的字符串拼接或基本的算數(shù)運算從 props 派生出來,那么沒理由將這些值包含在組件的 state 上。

1.2 Prop 和 Reconciliation

重要的是要記住,只要 props(或 state)的值不等于之前的值,React 就會觸發(fā)重新渲染。如果 props 或者 state 包含一個對象或者數(shù)組,嵌套值中的改變會觸發(fā)重新渲染。考慮到這一點,你需要注意在每次渲染的生命周期中,創(chuàng)建一個新的 props 或者 state 都可能無意中導(dǎo)致了性能下降。(注:對象或者數(shù)組只要引用不變,是不會觸發(fā)rerender的)

例子: 函數(shù)綁定的問題

/* 給 prop 傳入一個行內(nèi)綁定的函數(shù)(包括 ES6 箭頭函數(shù))實質(zhì)上是在每次父組件 render 時傳入一個新的函數(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 時 React 會將他們作為一個新的值。這在處理 Radium 或者行內(nèi)樣式時通常是有問題的。 *//* Bad */ // 每次渲染時都會為 style 新建一個對象字面量 render() {return <div style={ { backgroundColor: 'red' } }/> }/* Good */ // 在組件外聲明 const style = { backgroundColor: 'red' };render() {return <div style={ style }/> }

例子 : 注意兜底值字面量

/* 有時我們會在 render 函數(shù)中創(chuàng)建一個兜底的值來避免 undefined 報錯。在這些情況下,最好在組件外創(chuàng)建一個兜底的常量而不是創(chuàng)建一個新的字面量。 /* /* Bad */ render() {let thingys = [];// 如果 this.props.thingys 沒有被定義,一個新的數(shù)組字面量會被創(chuàng)建if( this.props.thingys ) {thingys = this.props.thingys;}return <ThingyHandler thingys={ thingys }/> }/* Bad */ render() {// 這在功能上和前一個例子一樣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)該是它直接需要的。為了將值傳給子組件而將一個大的、復(fù)雜的對象或者很多獨立的 props 傳遞給一個組件會導(dǎo)致很多不必要的組件渲染(并且會增加開發(fā)復(fù)雜性)。

我們使用 Redux 作為狀態(tài)容器,所以在我們看來,最理想的是方案在組件層次結(jié)構(gòu)的每一個層級中使用 react-redux 的 connect() 函數(shù)直接從 store 上獲取數(shù)據(jù)。connect 函數(shù)的性能很好,并且使用它的開銷也非常小。

1.4 組件方法

由于組件方法是為組件的每個實例創(chuàng)建的,如果可能的話,使用 helper/util 模塊的純函數(shù)或者靜態(tài)類方法。尤其在渲染大量組件的應(yīng)用中會有明顯的區(qū)別。

2. 進(jìn)階


image.png


視圖的變化是邪惡的

2.1 shouldComponentUpdate()

React 有一個生命周期函數(shù) shouldComponentUpdate()。這個方法可以根據(jù)當(dāng)前的和下一次的 props 和 state 來通知這個 React 組件是否應(yīng)該被重新渲染。

然而使用這個方法有一個問題,開發(fā)者必須考慮到需要觸發(fā)重新渲染的每一種情況。這會導(dǎo)致邏輯復(fù)雜,一般來說,會非常痛苦。如果非常需要,你可以使用一個自定義的shouldComponentUpdate()
方法,但是很多情況下有更好的選擇。

2.2 React.PureComponent

React 從 v15 開始會包含一個 PureComponent 類,它可以被用來構(gòu)建組件。React.PureComponent聲明了它自己的 shouldComponentUpdate() 方法,它自動對當(dāng)前的和下一次的 props 和 state 做一次淺對比。有關(guān)淺對比的更多信息,請參考這個 Stack Overflow:http://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react

在大多數(shù)情況下,React.PureComponent 是比 React.Component更好的選擇。在創(chuàng)建新組件時,首先嘗試將其構(gòu)建為純組件,只有組件的功能需要時才使用 React.Component。更多信息,請查閱相關(guān)文檔 React.PureComponent。

2.3 組件性能分析(在 Chrome 里)

在新版本的 Chrome 里,timeline 工具里有一個額外的內(nèi)置功能可以顯示哪些 React 組件正在渲染以及他們花費的時間。要啟用此功能,將 ?react_perf
作為要測試的 URL 的查詢字符串。React 渲染時間軸數(shù)據(jù)將位于 User Timing 部分。
更多相關(guān)信息,請查閱官方文檔:Profiling Components with Chrome Timeline 。

2.4 有用的工具: why-did-you-update

這是一個很棒的 NPM 包,他們給 React 添加補丁,當(dāng)一個組件觸發(fā)了不必要的重新渲染時,它會在控制臺輸出一個 console 提示。

注意: 這個模塊在初始化時可以通過一個過濾器匹配特定的想要優(yōu)化的組件,否則你的命令行可能會被垃圾信息填滿,并且可能你的瀏覽器會掛起或者崩潰,查閱 why-did-you-update 文檔獲取更多詳細(xì)信息。

3. 常見性能陷阱


image.png

3.1 setTimeout() 和 setInterval()

在 React 組件中使用 setTimeout() 或者 setInterval() 要十分小心。幾乎總是有更好的選擇,例如 'resize' 和 'scroll' 事件(注意:有關(guān)注意事項請參閱下一節(jié))。

如果你需要使用 setTimeout() 和 setInterval(),你必須遵守下面兩條建議

不要設(shè)置過短的時間間隔。

當(dāng)心那些小于 100 ms 的定時器,他們很可能是沒意義的。如果確實需要一個更短的時間,可以使用 window.requestAnimationFrame()。

保留對這些函數(shù)的引用,并且在 unmount 時取消或者銷毀他們。

setTimeout() 和 setInterval() 都返回一個延遲函數(shù)的引用,并且需要的時候可以取消它們。由于這些函數(shù)是在全局作用域執(zhí)行的,他們不在乎你的組件是否存在,這會導(dǎo)致報錯甚至程序卡死。

注意: 對 window.requestAnimationFrame() 來說也是如此

解決這個問題最簡答的方法是使用 react-timeout 這個 NPM 包,它提供了一個可以自動處理上述內(nèi)容的高階組件。它將 setTimeout/setInterval 等功能添加到包裝組建的 props 上。(特別感謝 Vixlet 的開發(fā)人員 Carl Pillot 提供這個方法)
如果你不想引入這個依賴,并且希望自行解決此問題,你可以使用以下的方法:

// 如何正確取消 timeouts/intervalscompnentDidMount() {this._timeoutId = setTimeout( this.doFutureStuff, 1000 );this._intervalId = setInterval( this.doStuffRepeatedly, 5000 ); } componentWillUnmount() {/*高級提示:如果操作已經(jīng)完成,或者值未被定義,這些函數(shù)也不會報錯*/clearTimeout( this._timeoutId );clearInterval( this._intervalId ); }

如果你使用 requestAnimationFrame() 執(zhí)行的一個動畫循環(huán),可以使用一個非常相似的解決方案,當(dāng)前代碼要有一點小的修改:

// 如何確保我們的動畫循環(huán)在組件消除時結(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() 不會拋出異常 }

3.2 未去抖頻繁觸發(fā)的事件

某些常見的事件可能會非常頻繁的觸發(fā),例如 scroll,resize
。去抖這些事件是明智的,特別是如果事件處理程序執(zhí)行的不僅僅是基本功能。Lodash 有 _.debounce 方法。在 NPM 上還有一個獨立的 debounce 包.
“但是我真的需要立即反饋 scroll/resize 或者別的事件”

我發(fā)現(xiàn)一種可以處理這些事件并且以高性能的方式進(jìn)行響應(yīng)的方法,那就是在第一次事件觸發(fā)時啟動 requestAnimationFrame() 循環(huán)。然后可以使用 debounce() 方法并且將 trailing 這個配置項設(shè)為 true
這意味著該功能只在頻繁觸發(fā)的事件流結(jié)束后觸發(fā))來取消對值的監(jiān)聽,看看下面這個例子。

class ScrollMonitor extends React.Component {constructor() {this.handleScrollStart = this.startWatching.bind( this );this.handleScrollEnd = debounce(this.stopWatching.bind( this ),100,{ leading: false, trailing: true } );}componentDidMount() {window.addEventListener( 'scroll', this.handleScrollStart );window.addEventListener( 'scroll', this.handleScrollEnd );}componentWillUnmount() {window.removeEventListener( 'scroll', this.handleScrollStart );window.removeEventListener( 'scroll', this.handleScrollEnd );//確保組件銷毀后結(jié)束循環(huán)this.stopWatching();}// 如果循環(huán)未開始,啟動它startWatching() {if( !this._watchFrame ) {this.watchLoop();}}// 取消下一次迭代stopWatching() {window.cancelAnimationFrame( this._watchFrame );}// 保持動畫的執(zhí)行直到結(jié)束watchLoop() {this.doThingYouWantToWatchForExampleScrollPositionOrWhatever()this._watchFrame = window.requestAnimationFrame( this.watchLoop )}}

3.3 密集CPU任務(wù)線程阻塞

某些任務(wù)一直是 CPU 密集型的,因此可能會導(dǎo)致主渲染線程的阻塞。舉幾個例子,比如非常復(fù)雜的數(shù)學(xué)計算,迭代非常大的數(shù)組,使用 File api 進(jìn)行文件讀寫,利用 <canvas> 對圖片進(jìn)行編碼解碼。

在這些情況下,如果有可能最好使用 Web Worker 將這些功能移到另一個線程上,這樣我們的主渲染線程可以保持順滑。

歡迎關(guān)注極客教程微信公眾號平臺:geekjc

轉(zhuǎn)載于:https://my.oschina.net/cllgeek/blog/1584704

總結(jié)

以上是生活随笔為你收集整理的React优化性能的经验教训的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。