react是用於構建用戶界面的JS框架。所以react只負責解決view層的渲染。前端
virtual dom 其實是對實際Dom的一個抽象,是一個js對象。react全部的表層操做其實是在操做virtual dom。
通過diff算法會計算出virtual dom的差別,而後將這些差別進行實際的dom操做更新頁面。react
setState是「異步」的,調用setState只會提交一次state修改到隊列中,不會直接修改this.state。算法
等到知足必定條件時,react會合並隊列中的全部修改,觸發一次update流程,更新this.state。性能優化
所以setState機制減小了update流程的觸發次數,從而提升了性能。框架
因爲setState會觸發update過程,所以在update過程當中必經的生命週期中調用setState會存在循環調用的風險。dom
另外用於監聽state更新完成,可使用setState方法的第二個參數,回調函數。在這個回調中讀取this.state就是已經批量更新後的結果。異步
在實際開發中,setState的表現有時會不一樣於理想狀況。主要是如下兩種。函數
在mount流程中調用setState。
在setTimeout/Promise回調中調用setState。
在第一種狀況下,不會進入update流程,隊列在mount時合併修改並render。性能
在第二種狀況下,setState將不會進行隊列的批更新,而是直接觸發一次update流程。學習
這是因爲setState的兩種更新機制致使的,只有在批量更新模式中,纔會是「異步」的。
diff算法用於計算出兩個virtual dom的差別,是react中開銷最大的地方。
傳統diff算法經過循環遞歸對比差別,算法複雜度爲O(n3)。
react diff算法制定了三條策略,將算法複雜度從 O(n3)下降到O(n)。
ID
來區分。針對這三個策略,react diff實施的具體策略是:
另外,在對比同一層級的子節點時:
diff算法會以新樹的第一個子節點做爲起點遍歷新樹,尋找舊樹中與之相同的節點。
若是節點存在,則移動位置。若是不存在,則新建一個節點。
在這過程當中,維護了一個字段lastIndex,這個字段表示已遍歷的全部新樹子節點在舊樹中最大的index。
在移動操做時,只有舊index小於lastIndex的纔會移動。
這個順序優化方案其實是基於一個假設,大部分的列表操做應該是保證列表基本有序的。
能夠推倒倒序的狀況下,子節點列表diff的算法複雜度爲O(n2)
因爲react中性能主要耗費在於update階段的diff算法,所以性能優化也主要針對diff算法。
減小diff算法觸發次數實際上就是減小update流程的次數。
正常進入update流程有三種方式:
setState機制在正常運行時,因爲批更新策略,已經下降了update過程的觸發次數。
所以,setState優化主要在於非批更新階段中(timeout/Promise回調),減小setState的觸發次數。
常見的業務場景即處理接口回調時,不管數據處理多麼複雜,保證最後只調用一次setState。
父組件的render必然會觸發子組件進入update階段(不管props是否更新)。此時最經常使用的優化方案即爲shouldComponentUpdate方法。
最多見的方式爲進行this.props和this.state的淺比較來判斷組件是否須要更新。或者直接使用PureComponent,原理一致。
須要注意的是,父組件的render函數若是寫的不規範,將會致使上述的策略失效。
// Bad case // 每次父組件觸發render 將致使傳入的handleClick參數都是一個全新的匿名函數引用。 // 若是this.list 一直都是undefined,每次傳入的默認值[]都是一個全新的Array。 // hitSlop的屬性值每次render都會生成一個新對象 class Father extends Component { onClick() {} render() { return <Child handleClick={() => this.onClick()} list={this.list || []} hitSlop={{ top: 10, left: 10}}/> } } // Good case // 在構造函數中綁定函數,給變量賦值 // render中用到的常量提取成模塊變量或靜態成員 const hitSlop = {top: 10, left: 10}; class Father extends Component { constructor(props) { super(props); this.onClick = this.onClick.bind(this); this.list = []; } onClick() {} render() { return <Child handleClick={this.onClick} list={this.list} hitSlop={hitSlop} /> } }
其中forceUpdate方法調用後將會直接進入componentWillUpdate階段,沒法攔截,所以在實際項目中應該棄用。
使用shouldComponentUpdate鉤子,根據具體的業務狀態,減小沒必要要的props變化致使的渲染。如一個不用於渲染的props致使的update。
另外, 也要儘可能避免在shouldComponentUpdate 中作一些比較複雜的操做, 好比超大數據的pick操做等。
不須要渲染的props,合理使用context機制,或公共模塊(好比一個單例服務)變量來替換。
以爲內容有幫助能夠關注下個人公衆號 「 前端e進階 」,掌握最新動態,一塊兒學習成長!