本文總結了一些編寫 React 代碼的建議,有助於理解 React 的渲染機制,主要是通過減少不必要的重新渲染來提升 React 性能。
回顧使用 React 開發的經歷,已有接近兩年了,一直想要寫一篇文章來介紹如何編寫高性能的 React 代碼,又或者是性能調優之類的,於是,我總結了一些編寫高性能 React 代碼的實踐,或許能讓你對 React 的理解更加深刻。
影響 React 性能的主要因素#
React 專注於構建用戶界面,內部通過 VirtualDOM 和 Diff 算法實現視圖的更新,本身擁有很高的性能。當組件的 state 和 props 改變的時候,React 就會重新 render,進而更新視圖。在 render 過程中,react 會將 JSX 語法轉換為虛擬 DOM,同時進行 Diff 算法比較新舊 DOM 的變化,從而對視圖進行局部更新。試想,當 render 執行後,JSX 轉換虛擬 DOM,然後 Diff,最後發現新舊 DOM 是一樣的,最後並沒有更新視圖。那麼,就導致了不必要的 render,造成了性能的浪費。所以,減少不必要的 render,成為優化 React 性能的主要因素。
state 只與視圖有關#
編寫 React 組件的時候,你肯定會用到變量,變量可以保存在 this 上,可以保存到 state 中,還可以使用全局變量。那麼什麼時候該使用 state 呢?答案是只有與視圖相關的變量才放在 state 中,與視圖無關的可以放在 this 上。如果與視圖更新無關的變量放在 state 中,而且這個變量需要頻繁的更新,而每次更新都需要重新 render,勢必造成性能浪費,使用 this 來存放無疑是更好的選擇。
shouldComponentUpdate#
考慮如下代碼:
export default class Home extends Component {
state = {
count: 0
}
add = () => {
this.setState({count: 0});
}
render() {
console.log('render');
return (
<div className="App">
<div onClick={this.add}>+++</div>
<p className="App-intro">
{this.state.count}
</p>
</div>
);
}
}
當點擊 +++ 觸發 add 事件時,setState 更新 state,但是新舊 state 完全一樣,React 並沒有在 Component 組件中進行優化,導致不必要的 render 執行。此時,需要我們手動進行優化,當 state 或者 props 更新後 react 組件的生命週期 shouldComponentUpdate 觸發,在 shouldComponentUpdate 中可以對新舊 state 和 props 進行比較,判斷組件是否需要重新 render。
shouldComponentUpdate(nextProps, nextState) {
if(nextState.count === this.state.count) {
return false;
}
}
默認情況下 shouldComponentUpdate 返回 true,即始終要重新渲染,此處我們使用 shouldComponentUpdate 進行比較後,返回 false,react 將不會重新 render。
當 state 為字符串等基本類型的時候,shouldComponentUpdate 還能應付自如,那如果 state 是對象或者數組等引用類型呢?對於引用類型只能對其進行遞歸比較才能判斷其是否相等,又或者是採用 JSON.stringify (nextState.obj) === JSON.stringify (this.state.obj) 進行比較,但是當對象嵌套層級較深或者函數、正則等類型時,shouldComponentUpdate 便失去了它的用武之地。
PureComponent#
React.PureComponent 與 React.Component 幾乎完全相同,但 React.PureComponent 通過 prop 和 state 的淺對比來實現 shouldComponentUpdate ()。
簡而言之,PureComponent 自己內部實現了一個 shouldComponentUpdate,而不需要我們再去編寫比較代碼。
React.PureComponent 的 shouldComponentUpdate () 只會對對象進行淺對比。如果對象包含複雜的數據結構,它可能會因深層的數據不一致而產生錯誤的否定判斷(表現為對象深層的數據已改變視圖卻沒有更新)。
測試表明,使用 PureComponent 的性能會略勝 shouldComponentUpdate 一籌。
StateLessComponent#
無狀態組件(StateLessComponent)又稱函數式組件。
const Text = ({ children = 'Hello World!' }) =>
<p>{children}</p>
函數式組件沒有 class 聲明,沒有生命週期,僅使用 render 方法實現,不存在組件實例化的過程,因此不需要分配多餘的內存,從而提升了渲染性能。
箭頭函數與 bind#
當我們在組件上綁定事件處理函數的時候,為了能正常訪問 state,通常會使用 bind 或者箭頭函數。
<Button onClick={this.update.bind(this)} />
又或者
<Button onClick={() => { console.log("Click"); }} />
在以上情況下,bind 每次都返回全新的函數,箭頭函數在每次 render 時都會重新分配(和使用 bind 的方式相同),對於組件來說每次綁定的都是新的函數,Button 組件在進行 props 比較的時候會認為前後 props 不一樣,造成重新渲染。
比較推薦的寫法有兩種:
update = () => {
//
}
<Button onClick={this.update} />
或者
constructor(props) {
super(props);
this.update = this.update.bind(this);
}
update() {
//
}
<Button onClick={this.update} />
內聯 style#
當我們為組件設置樣式的時候,可能會寫出如下代碼:
<Button style={{width: 100, marginTop: 50 }} />
這種寫法,每次 render 時 style 返回的都是全新的對象,我們知道,引用類型比較的是引用地址,每次的結果都是不相等,也會導致重新渲染。
推薦的寫法是使用 className。
<Button className="btn" />
屬性傳遞與 []#
當我們為組件設置缺省值的時候:
<RadioGroup options={this.props.options || []} />
如果每次 this.props.options 值都是 null 的話,意味著每次傳遞給都是字面量數組 [],但字面量數組和 new Array () 效果是一樣的,始終生成新的實例,所以表面上看雖然每次傳遞給組件的都是相同的空數組,其實對組件來說每次都是新的屬性,都会引起渲染。所以正確的方式應該將一些常用值以變量的形式保存下來:
const DEFAULT_OPTIONS = [];
<RadioGroup options={this.props.options || DEFAULT_OPTIONS} />
map 與 key#
在 react 中,你應該經常用到 map 來渲染一個列表,當你忘記為每個列表元素設置 key 屬性的時候,react 會在控制台發出警告 a key should be provided for list items。
實際上,Keys 可以在 DOM 中的某些元素被增加或刪除的時候幫助 React 識別哪些元素發生了變化。因此你應當給數組中的每一個元素賦予一個確定的標識。
一個元素的 key 最好是這個元素在列表中擁有的一個獨一無二的字符串。通常,我們使用來自數據的 id 作為元素的 key:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
當元素沒有確定的 id 時,你可以使用他的序列號索引 index 作為 key:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
當 todoItems 中數據發生改變的時候,使用索引作為 key 的列表都將重新渲染。所以不建議使用索引來進行排序,因為這會導致渲染變得很慢。
減少在 render 內執行變量#
我們已經知道,當組件的 state 和 props 發生改變的時候,render 都會執行,那麼,當 render 中有變量的執行或者函數執行的時候,可能會影響渲染性能,所以,應該減少在 render 內執行變量或者函數。
精簡 props 和 state#
從上面我們已經知道,當 props 和 state 為引用類型的時候,react 內部只對 props 和 state 進行淺比較,那麼,props 和 state 可以儘量設置為基礎數據類型,對象的嵌套層級不要過深,減少 props 的參數數量,使組件的 props 和 state 對象數據盡可能達到扁平化,都可以在一定程度上優化 react 的渲染性能。
總結#
本文討論了一系列編寫 React 代碼的原則,旨在從減少重新 render 的方面來提升 react 代碼的性能。性能優化一直是一個持久的話題,還可以從很多方面去考慮,比如,使用不可變的數據結構 immutable.js,使用 reselect 來緩存 redux 中的複雜計算,對組件儘可能進行拆分為子組件,都是值得探討的話題。那麼,下次再見。