性能優(yōu)化
在更新UI時(shí),React在內(nèi)部使用幾種巧妙的技術(shù),以此來最小化DOM的操作量,對(duì)于許多應(yīng)用來說,React不需要做太多的優(yōu)化工作就可以快速創(chuàng)建用戶界面。
使用生產(chǎn)版本
在React中檢測(cè)性能時(shí),需要使用壓縮過的生產(chǎn)版本。
在默認(rèn)情況下React中包含很多開發(fā)過程中需要的警告模塊,然而這會(huì)導(dǎo)致React更大更慢,因此在部署發(fā)布的時(shí)候,確認(rèn)使用的是生產(chǎn)版本,不是開發(fā)版本。
如果還不確定構(gòu)建的過程是否正確,可以安裝React開發(fā)者工具(chrome)。
(類似vue的開發(fā)者工具),當(dāng)你訪問一個(gè)生產(chǎn)模式的React頁(yè)面的時(shí)候,工具圖標(biāo)會(huì)有一個(gè)藍(lán)黑色的背景。
[圖片上傳失敗...(image-aefc3d-1534304297449)]
當(dāng)訪問一個(gè)開發(fā)模式的頁(yè)面的時(shí)候,會(huì)有一個(gè)紅色的背景。[圖片上傳失敗...(image-d7a398-1534304297450)]
建議在開發(fā)的時(shí)候使用開發(fā)模式,部署發(fā)布的時(shí)候使用生產(chǎn)模式。
以下是構(gòu)建生產(chǎn)應(yīng)用的流程。
Create React App 流程
如果你的項(xiàng)目是以Create React App創(chuàng)建的,運(yùn)行如下代碼:
cnpm run build //項(xiàng)目構(gòu)建
這將會(huì)在項(xiàng)目的 build/文件夾下創(chuàng)建一個(gè)生產(chǎn)版本的應(yīng)用。
注意只有發(fā)布正常版本的時(shí)候才需要這樣做,
平時(shí)正常開發(fā)只需要 cnpm start運(yùn)行就可以了。
單文件生產(chǎn)版本
使用已經(jīng)提供的React和ReactDOM文件即可
<script src="https://unpkg.com/react@15/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
注意只有結(jié)尾為.min.js的React文件才是適合生產(chǎn)使用的。
Brunch
為了創(chuàng)建最高效的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
接下來構(gòu)建生產(chǎn)版本,在build命令后添加-p參數(shù):
brunch build -p
注意只有生產(chǎn)版本需要這樣操作。不要在開發(fā)環(huán)境中安裝這個(gè)插件或者使用-p參數(shù),因?yàn)樗鼤?huì)隱藏掉有用的React警告并使構(gòu)建過程更慢。
為了構(gòu)建生產(chǎn)版本,務(wù)必添加這些設(shè)置指令 (參數(shù)很重要):
-
envify該插件確保正確的編譯環(huán)境,全局安裝(-g)。 -
uglifyify該插件移除了開發(fā)接口。全局安裝(-g)。 -
bundle-collapser該插件用數(shù)字替代了長(zhǎng)長(zhǎng)的模塊ID。 - 最后,以上結(jié)果都被輸添加至
uglify-js來得到整合。(了解原因).
Webpack
注意:
如果你正在使用Create React App方式,參考上述文檔。
本節(jié)只適用于直接配置Webpack的情況。
為了創(chuàng)建最高效的 webpack 生產(chǎn)版本,需要在生產(chǎn)版本的配置中添加這些插件。
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin()
了解更多參見Webpack文檔.
注意只有在生產(chǎn)環(huán)境中需要這樣做, 不需要再開發(fā)環(huán)境中安裝UglifyJsPlugin和DefinePlugin,因?yàn)樗麄儠?huì)忽略掉有用的Reacr警告,并且使得構(gòu)建的過程變得更慢。
使用 Chrome Performance 歸檔組件
在開發(fā)模式中,使用支持的瀏覽器內(nèi)的性能工具可以更加直觀的了解,組件何時(shí)掛載,更新和卸載。
[圖片上傳失敗...(image-4830a9-1534304297450)]
chrome瀏覽器內(nèi):
①在項(xiàng)目地址欄內(nèi)添加查詢字符串 ?react_perf(例如, http://localhost:3000/?react_perf)。
②打開Chrome開發(fā)工具Performance 標(biāo)簽頁(yè)點(diǎn)擊Record.
③執(zhí)行你想要分析的動(dòng)作。不要記錄超過20s,不然Chrome可能會(huì)掛起。
④停止記錄
⑤React事件將會(huì)被歸類在 User Timing標(biāo)簽下。
更多的詳細(xì)操作,請(qǐng)參考 BenSchwarz 的這篇文章。
注意由于這些數(shù)字是相對(duì)的,因此組件在生產(chǎn)版本中會(huì)運(yùn)行更快。然而,這也能夠幫助你了解何時(shí)會(huì)有無關(guān)的組件被錯(cuò)誤的更新,以及你的組件更新的深度和頻率。
目前瀏覽器中僅有Chrome,Edge和IE支持此特性,但是我們使用此標(biāo)準(zhǔn)用戶Timing API,因此我們期待更多的瀏覽器對(duì)其添加支持。
避免重復(fù)渲染
React在渲染出的UI內(nèi)部 建立和維護(hù)一個(gè)內(nèi)層的實(shí)現(xiàn)方式,他包括從組件返回的React元素。
這種實(shí)現(xiàn)方式使得React避免了一些不必要的創(chuàng)建和關(guān)聯(lián)dom的節(jié)點(diǎn)。因?yàn)檫@樣做可能比直接操作javascript對(duì)象會(huì)更慢一些。有時(shí)候被稱為“虛擬DOM”,但是他其實(shí)和 React Native的工作方式是一樣的。
當(dāng)一個(gè)組件的props或者state改變時(shí),React通過比較新返回的元素和之前渲染的元素,來決定是否有必要更新實(shí)際的DOM。當(dāng)他們不相等時(shí),會(huì)更新dom。
有一些情況下,你的組件可以通過重寫這個(gè)生命周期函數(shù)shouldComponentUpdate來提升速度,他在重新渲染過程開始之前觸發(fā)。這個(gè)函數(shù)默認(rèn)返回true,可以使用React執(zhí)行更新。
shouldComponentUpdate(nextProps,nextState){
return true;
}
如果你想要你的組件在某些情況下不需要更新,可以控制在這個(gè)生命周期函數(shù)中返回false來跳過整個(gè)渲染的過程,這個(gè)過程包括了之后對(duì)這個(gè)組件調(diào)用render()是指令。
shouldComponentUpdate()的應(yīng)用
這是一個(gè)組件子樹,對(duì)其中每個(gè)組件來說,scu 表明了每個(gè)組件 shouldComponentUpdate() 的返回內(nèi)容,vDOMEq表明了待渲染的React元素與原始元素是否相等,最后圓圈的顏色表示這個(gè)組件是否需重新要渲染。
[圖片上傳失敗...(image-7cbbeb-1534304297450)]
從圖中可知:
由于以C2為根的子樹shouldComponentUpdate返回了false,React不會(huì)渲染C2,甚至不會(huì)嘗試渲染C4和C5.
對(duì)于C1和C3來說,shouldComponentUpdate返回true,因此React會(huì)深入到分支中并檢查他們C6,C7,C8,其中C6的shouldComponentUpdate返回了true,由于待渲染的元素與原始元素不相等,React會(huì)更新這個(gè)DOM節(jié)點(diǎn)。
最后一個(gè)有趣的情況是C8,React需要渲染這個(gè)組件,但是由于組件元素的返回值與原元素相等,因此并沒有更新這個(gè)dom節(jié)點(diǎn)。
注意其中React只需要更新C6,因?yàn)樗遣豢杀苊獾?,?duì)于C8而言,他通過比較待渲染的元素和原始元素來避免了渲染。對(duì)于C2的子樹和C7,他們甚至都沒有執(zhí)行比較,因?yàn)槲覀冊(cè)O(shè)置了shouldComponentUpdate返回false,render并沒有調(diào)用。
案例:
如果只想讓組件在props.color或者state.count的值變化時(shí)重新渲染,你們可以像下面這樣設(shè)定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!==next.count){
return true;
}
return false;
}
render(){
return(
<button color={this.props.color}
onClick={()=>{this.setState(state=>({count:state.count+1}))}}
>
Count:{this.state.count}
</button>
)
}
}
在以上代碼中,shouldComponentUpdate只檢查props.color和state.count的變化,如果這些值都沒有變化,這些值就不會(huì)更新。
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(){
return(
<button color={this.props.color}
onClick={()=>{this.setState(state=>({count:state.count+1}))}}
>
Count:{this.state.count}
</button>
)
}
}
ReactDOM.render(
<CounterButton/>,document.getElementById('root')
)
當(dāng)你的組件變得更加復(fù)雜時(shí),你可以用類似的模式來作一個(gè)“淺比較”,用來比較屬性和值來判定是否需要組件更新,這種模式十分常見,因此React提供一個(gè)輔助的對(duì)象來實(shí)現(xiàn)這個(gè)邏輯。繼承自來自`React.PureComponent,一下的代碼簡(jiǎn)單的實(shí)現(xiàn)相同的操作。
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
大部分情況下,你可以使用React.PureComponent而不必寫你自己的shouldComponentUpdate,它只做一個(gè)淺比較。但是由于淺比較會(huì)忽略屬性或狀態(tài)突變的情況,此時(shí)你不能使用它。