React版本:15.4.2
**翻譯:xiyoki **
在內部现拒,React使用幾種聰明的技術來最小化更新UI所需的昂貴的DOM操作的數(shù)量。對于許多應用程序祭芦,使用React將導致快速的用戶界面钦睡,而無需進行大量工作來專門優(yōu)化性能。然而帖努,有幾種方法可以加快你的React應用程序。
Use The Production Build(使用生產(chǎn)構建)
在你的React應用程序中粪般,如果你遇到了基準測試或性能方面的問題拼余,請確保你正使用縮小的生產(chǎn)版本進行測試:
- 對于創(chuàng)建React應用程序,你需要運行
npm run build
,并按照說明進行操作亩歹。 - 對于單文件構建匙监,我們提供production-ready
min.js
版本。 - 對于Browserify小作,你用需要用
NODE_ENV=production
來運行它亭姥。 - 對于Webpack,你需要在你的生產(chǎn)配置中顾稀,將其添加到插件中:
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin()
- 對于Rollup达罗,你需要在CommonJS插件之前使用replace插件。因此静秆,development-only 模塊不被導入粮揉。完整的設置示例see this gist。
plugins: [
require('rollup-plugin-replace')({
'process.env.NODE_ENV': JSON.stringify('production')
}),
require('rollup-plugin-commonjs')(),
// ...
]
當構建你的應用程序時抚笔,development build 包含的額外警告十分有用扶认,但它額外的bookkeeping會讓其自身變慢。
Profiling Components with Chrome Timeline(使用Chrome時間軸分析組件)
在development模式下殊橙,你可以在支持的瀏覽器中使用性能工具來可視化組件的加載辐宾,更新和卸載狱从。例如:

在Chrome中執(zhí)行此操作:
- 通過在查詢字符串中加入
?react_perf
來加載你的應用程序(例如,http://localhost:3000/?react_perf
)叠纹。 - 打開Chrome DevTools Timeline選項卡矫夯,然后按住Record。
- 執(zhí)行你想配置的操作吊洼,記錄時間不要超過20秒训貌,否則Chrome可能會掛起。
- 停止記錄冒窍。
- React事件將在User Timing標簽下分組递沪。
請注意,數(shù)字是相對的综液,因此組件在生產(chǎn)中渲染得更快款慨。盡管如此,這應該可以幫助你了解到不相關的UI被錯誤更新谬莹,以及你的UI更新的深度和頻率檩奠。
目前,Chrome附帽,Edge和IE是唯一支持此功能的瀏覽器埠戳,但我們使用標準的User Timing API,因此我們希望更多的瀏覽器能支持此功能蕉扮。
Avoid Reconciliation
React構建并維護已渲染的UI的內部表示整胃。它包含從組件返回的React元素。此表示使React避免創(chuàng)建DOM節(jié)點和訪問現(xiàn)有的超出必要性的節(jié)點喳钟,因此它可能比Javascript對象上的操作更慢屁使。有時它被稱為‘虛擬DOM’,但是它在React Native上以相同的方式工作奔则。
當組件的props或state更改時蛮寂,React通過將新返回的元素與先前已渲染的元素進行比較,來決定是否需要實際的DOM更新易茬。當它們不相等時酬蹋,React將更新DOM。
在某些情況下疾呻,你的組件可以通過覆蓋在重新渲染過程開始前就觸發(fā)的生命周期函數(shù)shouldComponentUpdate
來加快這一切除嘹。該函數(shù)的默認實現(xiàn)是返回true
写半,讓React執(zhí)行更新:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
如果你知道在某些情況下你的組件不需要更新岸蜗,相反,你可以從shouldComponentUpdate
返回false
跳過整個渲染過程叠蝇,包括當前和之下組件上的render()
調用璃岳。
shouldComponentUpdate In Action
這是一個組件的子樹年缎。對于每一個節(jié)點,SCU指示shouldComponentUpdate
返回的內容铃慷,而vDOMEq指示渲染的React元素是否等效单芜。最后,圓圈的顏色表示組件是否必須reconciled 犁柜。

對于以C2為根的子樹洲鸠,由于
shouldComponentUpdate
返回false
,React沒有嘗試渲染C2馋缅,因此甚至不必在C4和C5上調用shouldComponentUpdate
扒腕。對于C1和C3,
shouldComponentUpdate
返回true
,所以React必須下到葉子檢查它們萤悴。對于C6瘾腰,shouldComponentUpdate
返回true
,并且因為渲染的元素不等價覆履,React不得不更新DOM。最后一個有趣的例子是C8硝全。React不得不渲染這個組件,但是由于它返回的React元素等于先前已渲染的元素伟众,所以它不必更新DOM。
注意React只需要為C6做DOM更新赂鲤,這是不可避免的噪径。對于C8,它通過比較已渲染的React元素解脫(bailed out)了数初。對于C2的子樹和C7,由于我們已從
shouldComponentUpdate
上解脫(bailed out)车摄,它甚至沒有必要對元素進行比較,并且render也沒有被調用仑鸥。
Examples
當props.color
或state.count
變量的改變是你組件改變的唯一方式時吮播,你可以使用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() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
這段代碼,shouldComponentUpdate
只是檢查props.color
或state.count
是否有任何變化眼俊。如果這些值不更改,組件不更新环戈。如果你的組件更復雜,你可以使用一個類似的模式在props
和state
的所有字段之間做一個淺比較院塞,以確定組件是否應該更新。
這個模式是常見的拦止,React提供了從React.PureComponent
繼承而來的logic-just的用法。所以這段代碼以一個更簡單的方法來實現(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>
);
}
}
大多數(shù)時候艺玲,你可以使用React.PureComponent
鞠抑,而不是自己寫shouldComponentUpdate
饭聚。它只做一個淺的比較搁拙,因此如果props或state以一個淺比較會錯過的方式被改變,你就不能使用它酪碘。
這可能是更復雜的數(shù)據(jù)結構的問題盐茎。例如兴垦,假設你想要一個ListOfWords
組件來渲染一個以逗號分隔的單詞列表字柠,有一個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
將在this.props.words
的新值和舊值之間做一個簡單的比較。由于該代碼在WordAdder
組件的handleClick
方法中改變words
數(shù)組西潘,因此this.props.words
的新值和舊值比較后依然相等。該ListOfWords
因此將不會更新相种,即使它應該渲染新的單詞东抹。
The Power Of Not Mutating Data
避免此問題的最簡單的方法是避免使用正作為props或state使用的mutating values沃测。例如缭黔,上面的handleClick方法可以用concat重寫為:
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
ES6支持數(shù)組的擴展語法,可以使這更容易别渔。如果你正在使用Create React App惧互,此語法默認可用哎媚。
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
};
你也可以重寫代碼喊儡,以類似的方式mutates對象,以避免mutation艾猜。例如,我們有一個名為colormap
的對象淤毛,我們要寫一個將colormap.right
改變成'blue'
的函數(shù)。我們可以寫:
function updateColorMap(colormap) {
colormap.right = 'blue';
}
要寫這個低淡,而不改變原始對象瞬项,我們可以使用Object.assign
方法:
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
updateColorMap
現(xiàn)在返回一個新對象,而不是改變舊的對象囱淋。Object.assign
是ES6新增的,需要polyfill胁孙。
有一個添加 object spread properties的Javascript 提議,使得更新對象更容易涮较,也不會有mutation:
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
如果你使用Create React App冈止,默認情況下,Object.assign
和object spread syntax都有效熙暴。
Using Immutable Data Structures(使用不可變數(shù)據(jù)結構)
Immutable.js 是另一個解決這個問題的方法慌盯。它提供了不可變掂器,持久的集合,通過結構共享工作:
- 不可變:一旦創(chuàng)建国瓮,集合不能在另一個時間點更改。
- 持久性:新集合可以從先前的集合和諸如set的mutation中創(chuàng)建而來禁漓。新集合創(chuàng)建后,原始集合仍然有效播歼。
- 結構共享:使用與原始集合相同的結構創(chuà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');
x === y; // false
在這種情況下咒循,由于在變異時返回一個新的引用x
,我們可以安全地假設x
已經(jīng)改變。
另外兩個可以幫助使用不可變數(shù)據(jù)的庫是 seamless-immutable和immutability-helper.
不可變的數(shù)據(jù)結構為你提供了一種便宜的方式來跟蹤對象的更改颖医,這是我們實現(xiàn)shouldComponentUpdate
所需要的裆蒸。這可以為你提供一個不錯的性能提升。