React 效能調優總結
React 效能調優總結
首先要說一個庫: why-did-you-update
, 地址:why-did-you-update, 利用這個庫可以在頁面上快速看到多餘渲染的問題:
因為多數情況下我們在React元件當中是不會去寫shouldComponentUpdate
這個hook來避免多餘渲染的,所以就造成了少量的效能浪費。雖然優化是個漫長的道路,過早優化是邪惡的,但做還是要去做的。
下面講一下基本的一些手段
shouldComponentUpdate
在React當中,每一次的setState
操作,都會讓Virtual DOM
去做diff操作,雖然虛擬DOM的計算很快,但是隨著元件越來越多,結構越來越複雜,當你改變某個簡單的state時,就會造成連帶很多Component的重新render
shouldComponentUpdate
鉤子來告訴React是否要更新元件
PureComponent
當然我們在實際開發過程當中,由於資料的複雜程度來說,基本是不會去寫shouldComponentUpdate
hook,這時就可以去使用PureComponent
類來宣告元件。
PureComponent
的前身是PureRenderMixin
,和Component的區別就在於元件在render
之前會自動執行一次shallowEqual(淺比較)
,就相當於元件的第一層props和state的資料如果沒有發生改變,render就不會去執行,從而減少了不必要的渲染。
根據React原始碼,如果元件是純元件(Pure Component),那麼一下比較是很容易理解的:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
為什麼說是淺比較呢?這個很好理解,js的引用資料型別就很好的說明了這一點:
{} === {} //false
[] === [] //false
當然,你也可以在shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
return nextProps.user.id === props.user.id;
}
如果是在PureComponent當中寫了這個鉤子,那麼它就會被優先執行。
當然,在資料結構和巢狀比較深的情況下,這個方案也就不太管用了,所以,我們在前期定義資料結構時也是一個很重要的環節,可以去避免不必要的渲染。
Immutable or Immer
Facebook在2014年就推出了這個庫: Immutable.js,用來使資料持久化。在資料建立後,就不得去改變,任何的增刪改操作都是true一個新的Immutable
物件:
import { Map } from "immutable";
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 }); const map2 = map1.set('b', 50); map1 !== map2; // true map1.get('b'); // 2 map2.get('b'); // 50 map1.get('a') === map2.get('a'); // true
ImmutableJS
最大的兩個特性就是: immutable data structures
(永續性資料結構)與 structural sharing
(結構共享),永續性資料結構保證資料一旦建立就不能修改,使用舊資料建立新資料時,舊資料也不會改變,不會像原生 js 那樣新資料的操作會影響舊資料。而結構共享是指沒有改變的資料共用一個引用,這樣既減少了深拷貝的效能消耗,也減少了記憶體。比如下圖:
ImmutableJS
的API過於複雜,而且我也沒有用redux
,而是採用了Mutable
的Mobx
,看見Mobx
的作者寫了一個庫Immer,相對於ImmutableJS
比較簡單:
import produce from "immer"
/** * Classic React.setState with a deep merge */
onBirthDayClick1 = () => { this.setState(prevState => ({ user: { ...prevState.user, age: prevState.user.age + 1 } })) } /** * ...But, since setState accepts functions, * we can just create a curried producer and further simplify! */ onBirthDayClick2 = () => { this.setState( produce(draft => { draft.user.age += 1 }) ) }
程式碼上的優化
少用bind
每次bind都會返回一個新函式,重複建立靜態函式會浪費效能。最好直接使用箭頭函式繫結或者利用閉包直接把處理函式傳入子元件
setState優化
在我們去setState時,最好用新值去覆蓋舊值,而不是修改原值。 對於陣列,我們採用es6的spread語法:
this.setState(prevState => ({
words: [...prevState.words, 'marklar'], }));
對於物件,我們採用Object.assign或spread:
this.setState({
a: Object.assign({}, this.state.a, {b: '2222'}) }) //或者 this.setState({ a: {...this.state.a, {b: '222'}} })
不要在PureComponent元件的props使用直接賦值的方式
style={ { width: '100px' } } 這樣的做法傳入元件會造成重複渲染
這樣的方式會使shallowEqual
一定返回false
正確的方式:
const YourStyle = { width: '100px' }
return (
<YourComponent style={YourStyle}></YourComponent> )
或者我們直接就用上面說到的Immutable.js 或者 Immer.js 來處理