1. 基本原理
1.1 render()函數
一般來說,要盡可能少地在 render 函數中做操作。如果非要做一些復雜操作或者計算,也許你可以考慮使用一個 memoized 函數以便于緩存那些重復的結果。可以看看 Lodash.memoize,這是一個開箱即用的記憶函數。
反過來講,避免在組件的 state 上存儲一些容易計算的值也很重要。舉個例子,如果 props 同時包含 firstName 和 lastName,沒必要在 state 上存一個 fullName,因為它可以很容易通過提供的 props 來獲取。如果一個值可以通過簡單的字符串拼接或基本的算數運算從 props 派生出來,那么沒理由將這些值包含在組件的 state 上。
1.2 Prop 和 state
重要的是要記住,只要 props(或 state)的值不等于之前的值,React 就會觸發重新渲染。如果 props 或者 state 包含一個對象或者數組,嵌套值中的改變會觸發重新渲染。考慮到這一點,你需要注意在每次渲染的生命周期中,創建一個新的 props 或者 state 都可能無意中導致了性能下降。(注:對象或者數組只要引用不變,是不會觸發rerender的)
舉幾個現在我在開發中遇到的幾個問題:
例子: 函數綁定的問題
/*
給 prop 傳入一個行內綁定的函數(包括 ES6 箭頭函數)實質上是在每次父組件 render 時傳入一個新的函數。
*/
render() {
return (
<div>
<a onClick={() => this.onRender()}>顯示</a>
<a onClick={() => this.onRender.bind(this)}>顯示</a>
</div>
);
}
/*
應該在構造函數中處理函數綁定并且將已經綁定好的函數作為 prop 的值
*/
constructor( props ) {
this.doSomething = this.doSomething.bind( this );
//or
this.doSomething = (...args) => this.doSomething(...args);
}
render() {
return (
<div>
<a onClick={ this.onRender }>顯示</a>
</div>
);
}
例子: 對象或數組字面量
/*
對象或者數組字面量在功能上來看是調用了 Object.create() 和 new Array()。這意味如果給 prop 傳遞了對象字面量或者數組字面量。每次render 時 React 會將他們作為一個新的值。這在處理 行內樣式時通常是有問題的。
*/
/*遇到的寫法 */
// 每次渲染時都會為 style 新建一個對象字面量
render() {
return <div style={ { backgroundColor: 'red' } }/>
}
/* 建議寫法 */
// 在組件外聲明
const style = { backgroundColor: 'red' };
render() {
return <div style={ style }/>
}
例子 : 注意兜底值字面量
/*
有時我們會在 render 函數中創建一個兜底的值來避免 undefined 報錯。在這些情況下,最好在組件外創建一個兜底的常量而不是創建一個新的字面量。
/*
/* 遇到的寫法 */
render() {
let thingys = [];
// 如果 this.props.thingys 沒有被定義,一個新的數組字面量會被創建
if( this.props.thingys ) {
thingys = this.props.thingys;
}
return <ThingyHandler thingys={ thingys }/>
}
/* 遇到的寫法 */
render() {
// 這在功能上和前一個例子一樣
return <ThingyHandler thingys={ this.props.thingys || [] }/>
}
/* 建議的寫法*/
// 在組件外部聲明
const NO_THINGYS = [];
render() {
return <ThingyHandler thingys={ this.props.thingys || NO_THINGYS }/>
}
1.3 盡可能的保持 Props(和 State)簡單和精簡
理想情況下,傳遞給組件的 props 應該是它直接需要的。為了將值傳給子組件而將一個大的、復雜的對象或者很多獨立的 props 傳遞給一個組件會導致很多不必要的組件渲染(并且會增加開發復雜性)。
我們使用 Redux 作為狀態容器,所以在我們看來,最理想的是方案在組件層次結構的每一個層級中使用 react-redux 的 connect() 函數直接從 store 上獲取數據。connect 函數的性能很好,并且使用它的開銷也非常小。
這里已經優化了。
1.4 組件方法
由于組件方法是為組件的每個實例創建的,如果可能的話,使用 helper/util 模塊的純函數或者靜態類方法。尤其在渲染大量組件的應用中會有明顯的區別。
2. 組件是否重新渲染
視圖的變化是邪惡的
2.1 shouldComponentUpdate()
React 有一個生命周期函數 shouldComponentUpdate()。這個方法可以根據當前的和下一次的 props 和 state 來通知這個 React 組件是否應該被重新渲染。
然而使用這個方法有一個問題,開發者必須考慮到需要觸發重新渲染的每一種情況。這會導致邏輯復雜,一般來說,會非常痛苦。如果非常需要,你可以使用一個自定義的shouldComponentUpdate()
方法,但是很多情況下有更好的選擇。
2.2 React.PureComponent
React 從 v15 開始會包含一個 PureComponent 類,它可以被用來構建組件。React.PureComponent聲明了它自己的 shouldComponentUpdate() 方法,它自動對當前的和下一次的 props 和 state 做一次淺對比。有關淺對比的更多信息,請參考這個 Stack Overflow:http://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react
在大多數情況下,React.PureComponent 是比 React.Component更好的選擇。在創建新組件時,首先嘗試將其構建為純組件,只有組件的功能需要時才使用 React.Component。更多信息,請查閱相關文檔 React.PureComponent。
2.3 怎么使用PureComponent
PureComponent節約了我們的時間,避免了多余的代碼。那么,掌握如何正確使用它是非常重要的,否則如果使用不當,它就無法發揮作用。因為PureComponent僅僅是淺比較(shadow comparison),所以改變組件內部的 props 或者 state ,它將不會發揮作用。所以建議按上面建議的幾種寫法來開發。這樣如果用上面建議的寫法來寫的話就有兩個好處:
- 一是避免組件每次渲染都做重復的事
- 二是避免在使用PureComponent時損失PureComponent的優點
注意:
使用了pureComponent之后,只是淺比較,這樣會導致改變組件內部的props之后不會更新視圖,所以pureComponent建議用在純UI類的組件里。
正常情況下,遷移的方式非常簡單,就像改變組件繼承的基類,從
class MyComponent extends Component {...}
到
class MyComponent extends PureComponent {...}
在PureComponent出現之前,通常是自己實現一個這樣的組件:
import React,{ Component }from 'react'
import shallowEqual from 'react-pure-render/shallowEqual'
export default class PureComponent extends Component{
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
}
當然react性能優化不僅僅有上面提到的幾點,還可以從webpack,使用immutable.js上優化