?
This document uses PHP Chinese website manual Release
在內(nèi)部,React 使用幾種聰明的技術(shù)來最小化更新 UI 所需的昂貴 DOM 操作的數(shù)量。對于許多應(yīng)用程序而言,使用 React 將導(dǎo)致快速的用戶界面,而無需專門針對性能進(jìn)行優(yōu)化。不過,有幾種方法可以加速您的 React 應(yīng)用程序。
如果您在 React 應(yīng)用程序中進(jìn)行基準(zhǔn)測試或遇到性能問題,請確保您正在使用縮小的生產(chǎn)版本進(jìn)行測試。
默認(rèn)情況下,React 包含許多有用的警告。這些警告在開發(fā)中非常有用。但是,他們會使React變得越來越大,所以您應(yīng)該確保在部署應(yīng)用程序時使用生產(chǎn)版本。
如果您不確定您的構(gòu)建過程是否設(shè)置正確,可以通過安裝適用于 Chrome 的 React Developer Tools 進(jìn)行檢查。如果您在生產(chǎn)模式下使用 React 訪問網(wǎng)站,該圖標(biāo)將具有黑色背景:
如果您以開發(fā)模式訪問 React 網(wǎng)站,該圖標(biāo)將具有紅色背景:
預(yù)計(jì)在使用應(yīng)用程序時使用開發(fā)模式,在將應(yīng)用程序部署到用戶時使用生產(chǎn)模式。
您可以在下面找到有關(guān)為您的應(yīng)用生成應(yīng)用的說明。
如果您的項(xiàng)目是使用 Create React App 構(gòu)建的,請運(yùn)行:
npm run build
這將在build/
您的項(xiàng)目文件夾中創(chuàng)建您的應(yīng)用程序的生產(chǎn)版本。
請記住,這只是在部署到生產(chǎn)之前需要的。對于正常的開發(fā),使用npm start
。
我們提供 React 和 React DOM 的生產(chǎn)就緒版本作為單個文件:
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
請記住,只有以結(jié)尾的 React 文件.production.min.js
適用于生產(chǎn)。
為了最有效的 Brunch 生產(chǎn)版本,安裝uglify-js-brunch
插件:
# If you use npm npm install --save-dev uglify-js-brunch # If you use Yarn yarn add --dev uglify-js-brunch
然后,要創(chuàng)建生產(chǎn)版本,請將-p
標(biāo)志添加到build
命令中:
brunch build -p
請記住,您只需要為生產(chǎn)構(gòu)建執(zhí)行此操作。你不應(yīng)該-p
在開發(fā)中通過標(biāo)志或應(yīng)用這個插件,因?yàn)樗鼤[藏有用的 React 警告,并且使構(gòu)建慢得多。
對于最高效的 Browserify 生產(chǎn)版本,安裝一些插件:
# If you use npm npm install --save-dev envify uglify-js uglifyify # If you use Yarn yarn add --dev envify uglify-js uglifyify
要創(chuàng)建生產(chǎn)版本,請確保您添加這些轉(zhuǎn)換(順序很重要):
envify
變換確保正確的編譯環(huán)境設(shè)置。使其成為全球(-g
)。
uglifyify
轉(zhuǎn)換消除了開發(fā)導(dǎo)入。使它成為全局(-g
)。
最后,產(chǎn)生的束被傳送到束縛uglify-js
(閱讀為什么)。
例如:
browserify ./index.js \ -g [ envify --NODE_ENV production ] \ -g uglifyify \ | uglifyjs --compress --mangle > ./bundle.js
注意: 包名稱是
uglify-js
,但它提供的二進(jìn)制文件被調(diào)用uglifyjs
。這不是一個錯字。
請記住,您只需要為生產(chǎn)構(gòu)建執(zhí)行此操作。你不應(yīng)該在開發(fā)中應(yīng)用這些插件,因?yàn)樗鼈儠[藏有用的 React 警告,并使構(gòu)建更慢。
要獲得最高效的匯總生產(chǎn)版本,請安裝一些插件:
# If you use npm npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify # If you use Yarn yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify
要創(chuàng)建生產(chǎn)版本,請確保您添加這些插件(該訂單很重要):
replace
插件確保設(shè)置正確的構(gòu)建環(huán)境。
commonjs
插件提供對 Rollup 中 CommonJS 的支持。
uglify
插件壓縮和軋液最終束。
plugins: [ // ... require('rollup-plugin-replace')({ 'process.env.NODE_ENV': JSON.stringify('production') }), require('rollup-plugin-commonjs')(), require('rollup-plugin-uglify')(), // ...]
有關(guān)完整的設(shè)置示例,請參閱此要點(diǎn)。
請記住,您只需要為生產(chǎn)構(gòu)建執(zhí)行此操作。您不應(yīng)該在開發(fā)中將uglify
插件或replace
插件應(yīng)用于'production'
值,因?yàn)樗鼈儠[藏有用的 React 警告,并且使構(gòu)建更慢。
注意: 如果您使用 Create React App,請按照上述說明操作。本節(jié)僅與直接配置 webpack 有關(guān)。
要獲得最高效的 webpack 生產(chǎn)版本,請確保將這些插件包含在您的生產(chǎn)配置中:
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production')}),new webpack.optimize.UglifyJsPlugin()
你可以在 webpack 文檔中了解更多。
請記住,您只需要為生產(chǎn)構(gòu)建執(zhí)行此操作。你不應(yīng)該在開發(fā)中應(yīng)用UglifyJsPlugin
或者DefinePlugin
具有'production'
價值,因?yàn)樗鼈儠[藏有用的React警告,并且使構(gòu)建變得更慢。
在開發(fā)模式中,您可以使用受支持的瀏覽器中的性能工具來可視化組件的安裝,更新和卸載方式。例如:
在 Chrome 中執(zhí)行此操作:
在查詢字符串中?react_perf
加載您的應(yīng)用程序(例如,http://localhost:3000/?react_perf
)。
打開 Chrome DevTools 性能選項(xiàng)卡并按下記錄。
執(zhí)行你想要分析的動作。記錄時間不要超過20秒,否則 Chrome 可能會掛起。
停止錄制。
5. 反應(yīng)事件將被分組在User Timing標(biāo)簽下。
請注意,這些數(shù)字是相對的,因此組件在生產(chǎn)中會呈現(xiàn)更快速度 不過,這應(yīng)該有助于您意識到不相關(guān)的用戶界面被錯誤更新的時間,以及用戶界面更新發(fā)生的頻率和頻率。
目前Chrome,Edge和IE是唯一支持此功能的瀏覽器,但我們使用標(biāo)準(zhǔn)的User Timing API,因此我們希望更多瀏覽器為其增加支持。
React構(gòu)建并維護(hù)呈現(xiàn)的UI的內(nèi)部表示。它包含從組件返回的React元素。這種表示讓React避免了創(chuàng)建DOM節(jié)點(diǎn)和訪問現(xiàn)有的節(jié)點(diǎn),因?yàn)檫@可能比JavaScript對象上的操作慢。有時它被稱為“虛擬DOM”,但它在React Native上的工作方式相同。
當(dāng)組件的道具或狀態(tài)發(fā)生變化時,React通過比較新返回的元素和先前渲染的元素來決定是否需要實(shí)際的DOM更新。當(dāng)它們不相等時,React將更新DOM。
在某些情況下,您的組件可以通過重寫生命周期函數(shù)來加速所有這些,生命周期函數(shù)shouldComponentUpdate
在重新呈現(xiàn)過程開始之前觸發(fā)。該函數(shù)的默認(rèn)實(shí)現(xiàn)返回true
,使React執(zhí)行更新:
shouldComponentUpdate(nextProps, nextState) { return true;}
如果你知道,在某些情況下,你的組件并不需要更新,您可以返回false
從shouldComponentUpdate
而是跳過整個渲染過程,包括調(diào)用render()
此組件和下方。
這是一個組件的子樹。對于每一個,SCU
指示shouldComponentUpdate
返回的內(nèi)容,并vDOMEq
指出呈現(xiàn)的React元素是否相同。最后,圓圈的顏色表示組件是否需要調(diào)和。
由于以C2為基礎(chǔ)的子樹shouldComponentUpdate
返回false
,因此React不會嘗試渲染C2,因此甚至不必shouldComponentUpdate
在C4和C5上調(diào)用。
對于C1和C3,shouldComponentUpdate
返回true
,所以React必須下到葉子并檢查它們。對于shouldComponentUpdate
返回的C6 true
,由于渲染的元素不相同,React必須更新DOM。
最后一個有趣的案例是C8。React必須渲染這個組件,但是由于它返回的React元素與之前渲染的元素相同,所以它不必更新DOM。
請注意,React只需對C6進(jìn)行DOM突變,這是不可避免的。對于C8,它通過比較渲染的React元素和C2的子樹和C7來保護(hù),甚至不需要比較我們保存的元素shouldComponentUpdate
,render
也沒有調(diào)用。
如果你的組件改變的唯一方式是當(dāng)變量props.color
或state.count
變量發(fā)生變化時,你可以shouldComponentUpdate
檢查:
class CounterButton extends React.Component { constructor(props) { super(props); this.state = {count: 1}; } shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; } render() { <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> }}
在這段代碼,shouldComponentUpdate
只是檢查是否有任何變化props.color
或state.count
。如果這些值不會更改,則組件不會更新。如果你的組件變得更加復(fù)雜,你可以使用類似的模式在組件的所有字段之間進(jìn)行“淺層比較” props
,state
以確定組件是否應(yīng)該更新。這種模式很普遍,React提供了一個幫助器來使用這個邏輯 - 只是從中繼承而來的React.PureComponent
。所以這段代碼是一個簡單的方法來實(shí)現(xiàn)同樣的事情:
class CounterButton extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; } render() { <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> }}
大多數(shù)時候,你可以使用React.PureComponent
而不是寫自己的shouldComponentUpdate
。它只是進(jìn)行淺層比較,所以如果道具或狀態(tài)可能以淺層比較錯過的方式進(jìn)行了變異,則不能使用它。
這可能是一個更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的問題。例如,假設(shè)您希望ListOfWords
組件呈現(xiàn)以逗號分隔的單詞列表,并使用父WordAdder
組件通過單擊按鈕將單詞添加到列表中。此代碼無法正常工作:
class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; }} class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { // This section is bad style and causes a bug const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return ( <div><button onClick={this.handleClick} /><ListOfWords words={this.state.words} /></div> ); } }
問題是PureComponent
將會對舊的和新的值進(jìn)行簡單的比較this.props.words
。由于這段代碼words
在handleClick
方法中改變了數(shù)組,所以即使數(shù)組中的實(shí)際字已經(jīng)改變WordAdder
,舊的和新的值this.props.words
也會相等。該ListOfWords
因此將不再更新,即使它有768,16呈現(xiàn)新詞。
避免此問題的最簡單方法是避免將您正在用作道具或狀態(tài)的值進(jìn)行變異。例如,handleClick
上面的方法可以使用concat
as 重寫:
handleClick() { this.setState(prevState => ({ words: prevState.words.concat(['marklar']) }));}
ES6支持?jǐn)?shù)組的擴(kuò)展語法,這可以使這更容易。如果您正在使用Create React App,則默認(rèn)情況下此語法可用。
handleClick() { this.setState(prevState => ({ words: [...prevState.words, 'marklar'], }));};
您也可以以類似的方式重寫改變對象以避免突變的代碼。例如,假設(shè)我們有一個名為object的對象,colormap
并且我們想要編寫一個更改colormap.right
為的函數(shù)'blue'
。我們可以寫:
function updateColorMap(colormap) { colormap.right = 'blue';}
要寫這個而不改變原始對象,我們可以使用Object.assign方法:
function updateColorMap(colormap) { return Object.assign({}, colormap, {right: 'blue'});}
updateColorMap
現(xiàn)在返回一個新對象,而不是改變舊對象。Object.assign
在ES6中,需要填充。
JavaScript提議添加對象傳播屬性,以便更容易更新對象而無需進(jìn)行突變:
function updateColorMap(colormap) { return {...colormap, right: 'blue'};}
如果您使用的是創(chuàng)建React應(yīng)用程序,Object.assign
則默認(rèn)情況下都可以使用對象傳播語法。
Immutable.js是解決這個問題的另一種方法。它提供了通過結(jié)構(gòu)共享工作的不變的,持久的集合:
Immutable:一旦創(chuàng)建,一個集合不能在另一個時間點(diǎn)被修改。
Persistent:可以根據(jù)以前的集合和諸如集合之類的變體創(chuàng)建新集合。新集合創(chuàng)建后,原始集合仍然有效。
Structural Sharing:使用與原始集合盡可能多的相同結(jié)構(gòu)創(chuàng)建新集合,盡量減少復(fù)制以提高性能。
不變性使追蹤變化便宜。更改總是會導(dǎo)致一個新的對象,所以我們只需要檢查對象的引用是否已經(jīng)改變。例如,在這個常規(guī)的 JavaScript 代碼中:
const x = { foo: 'bar' };const y = x;y.foo = 'baz';x === y; // true
雖然y
是編輯過的,但由于它是對同一個對象的引用,所以x
此比較返回true
。你可以用 immutable.js 編寫類似的代碼:
const SomeRecord = Immutable.Record({ foo: null }); const x = new SomeRecord({ foo: 'bar' }); const y = x.set('foo', 'baz');const z = x.set('foo', 'bar'); x === y; // falsex === z; // true
在這種情況下,由于在變異時返回一個新的引用x
,所以我們可以使用引用相等性檢查(x === y)
來驗(yàn)證存儲的新值與存儲在y
其中的原始值不同x
。
另外兩個可以幫助使用不可變數(shù)據(jù)的庫是無縫不可變和不可變的幫助器。
不可變的數(shù)據(jù)結(jié)構(gòu)為您提供了一種便捷的方式來跟蹤對象的變化,這是我們需要實(shí)現(xiàn)的shouldComponentUpdate
。這通??梢詾槟闾峁┖芎玫男阅芴嵘?。