react效能優化
寫在前面的話:
要想解決問題,首先得找到問題的根源,所以,說起效能分析,還是要從其生命週期和渲染機制說起。
1.渲染機制
react的元件渲染分為初始化渲染和更新渲染,在初始化渲染的時候會呼叫根元件下的所有元件的render方法進行渲染。
但是當我們要更新某個子元件的時候,是從根元件傳遞下來應用在子元件上的資料發生改變。
我們只是希望呼叫關鍵路徑上元件的render就好了。
但是,
react的預設做法是呼叫所有元件render,再對生成的虛擬DOM進行對比,如不變就不進行更新。這樣的render和虛擬DOM的對比明顯實在浪費時間。
注意:
拆分元件是有利於複用和元件優化的,其次,生成虛擬DOM並進行對比發生在render之後,而不是render之前。
總得來說,父親元件的props和state發生變化時,他和他的子子孫孫元件等後代都要重新渲染。
2.更新階段的生命週期
1.componementWillReceiveProp:當掛載的元件接受到新的props時會被呼叫,此方法應該被用於比較this.props和nextProps以用於使用this.setState()執行狀態轉換,(元件內部資料有變化,使用state,但是在更新階段又要在props改變的時候改變state,則在這個生命週期裡面)
2.shouldComponentUpdate:當元件決定任何改變是否要更新到DOM時被呼叫,作為一個優化實現比較this.props和nextProps,this.state和nextState,如果應該跳過更新,返回false。
3.componentWillUpdate:在更新發生前被立即呼叫,此時不能呼叫this.setState()
4.componentDidUpdate:在更新發生後被立即呼叫。(可以在DOM更新完之後,做一些收尾工作)
react的優化是基於shouldComponentUpdate的,該生命週期預設返回true,所以一旦prop或state有任何變化,都會引起重新render。
所以,可以得出react的效能優化就是圍繞shouldComponentUpdate(SCU)方法來進行的,其優化無外乎兩點:
- 縮短SCU方法的執行時間(或者直接不執行)
- 沒必要的渲染,SCU就該返回false
shouldComponentUpdate:
react在每個元件生命週期更新的時候都會呼叫一個shouldComponentUpdate(nextprops,nextState)
函式,他的職責就是返回true,或者false,true表示需要更新,false就是表示不需要,預設值是返回true的,即便你沒有顯示的定義shouldComponentUpdate函式,他的返回值還是true。
根據渲染流程,首先會判斷shouldcomponentUpdate是否需要更新,如果需要更新,則呼叫元件的render生成新的虛擬DOM,然後再與舊的虛擬DOM進行對比,如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM,如果scu不需要更新,則直接保持不變,同時其子元素也保持不變。
注意:錯誤寫法
- {…this.props}(不能濫用,只傳遞component需要的props即可,傳遞的太多,或者層次傳的太深,都會加重shuoldComponentUpdate,其裡面的資料比較負擔,因此一定要慎重使用
spread attributes(<Component {...props}/>))
::this.handleChange()
(將該方法的bind一律置於constructor)- 複雜的頁面不要在一個元件中寫完
- 儘量使用
const element
定義 - map裡面新增key,並且key不要使用index(可變的)
- 儘量少的使用setTimeout或者不可控的refs,DOM操作
- props和state的資料儘可能簡單明瞭,扁平化。
- 使用return null而不是css的
display:none
來控制節點的隱藏。保證同一時間頁面的DOM節點儘可能的少。
3.效能分析工具
React特色工具:Perf
react官方提供一個外掛react.addons.perf可以幫助我們分析元件的效能,以確認是否需要優化。
Perf 是react官方提供的效能分析工具。Perf最核心的方法莫過於Perf.printWasted(measurements)
,該方法會列出那些沒必要的元件渲染。很大程度上,React的效能優化就是幹掉這些無謂的渲染。
操作:
開啟console面板,先輸入Perf.start()執行一些元件操作,引起資料變動,元件更新,然後輸入Perf.stop()。(建議一次只執行一個操作,好進行分析)
然後在輸入Perf.printInclusive將所有涉及到的元件render列印下來。(官方圖片)
或者輸入Perf.printWasted()檢視不需要的浪費元件的render。
優化前:
優化後:
4.其它檢測工具
react-perf-tool為react提供了一種視覺化的效能檢測方案,其同樣是基於React.addons,但是使用圖示來顯示結果更加方便。
1.PureRenderMixin(基於es5)
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
2.Shallow Compare(基於es6)
var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}
pureRender很簡單,就是把穿進來的component的shouldComponentUpdate給重寫掉,原來的shouldComponentUpdate,無論怎樣都是return true
,現在不會這樣了,我要用shallowCompare比一比,shallowCompare程式碼及其簡單,如下:
function shallowCompare(instance, nextProps, nextState) {
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
但是這樣做還是有缺點的:
shallowEqual其實只比較props的第一層,子屬性是不是相同,如果props是如下:
{
detail: {
name: "123",
age: "123"
}
}
他只會比較props.detail===nextProps.detail
,這就會導致在傳入複雜的資料的情況下,優化會失效。
7.補充
react在15.3.0裡面加入了react.PureComponent一個可繼承的新的基礎類,用來替換react-addons-pure-render-mixin,用法如下:
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>
);
}
}
8.immutable.js
我們也可以在shouldComponentUpdate()中使用deepCopy和deepCompare來避免無必要的render(),但是deepCopy和deepCompare一般都是非常耗效能的。
Immutable Date就是一旦建立,就不能再被更改的資料。對Immutable物件的任何修改或者新增刪除操作都會返回一個新的Immutable物件。
Immutable實現的原理是Persistent Data Structure(持久化資料結構),也就是使用舊資料建立新資料時,要保證舊資料同時可用且不變,同時為了避免deeepCopy把所有的節點都複製一遍帶來的效能損耗,Immutable使用了,Structural Sharing(結構共享)即如果物件樹中一個節點發生變化,只修改這個節點和受他影響的父節點,其他節點則進行共享。
Immutable則提供了簡潔高效的判斷資料是否變化的方法,只需要===和is比較就能知道是否需要執行render(),而這個操作幾乎0成本,所以可以極大提高效能,修改後的shouldComponentUpdate是這樣的:
import { is } from 'immutable';
shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
const thisProps = this.props || {}, thisState = this.state || {};
if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}
for (const key in nextProps) {
if (!is(thisProps[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}