關於react性能優化,在react 16這個版本,官方推出fiber,在框架層面優化了react性能上面的問題。因爲這個太過於龐大,咱們今天圍繞子自組件更新策略,從兩個及其微小的方面來談react性能優化。 其主要目的就是防止沒必要要的子組件渲染更新。css
首先咱們看個例子,父組件以下:react
import React,{Component} from 'react'; import ComponentSon from './components/ComponentSon'; import './App.css'; class App extends Component{ state = { parentMsg:'parent', sonMsg:'son' } render(){ return ( <div className="App"> <header className="App-header" onClick={()=> {this.setState({parentMsg:'parent' + Date.now()})}}> <p> {this.state.parentMsg} </p> </header> <ComponentSon sonMsg={this.state.sonMsg}/> </div> );} } export default App;
父親組件做爲容器組件,管理兩個狀態,一個parentMsg用於管理自身組件,一個sonMsg用於管理子組件狀態。兩個點擊事件用於分別修改兩個狀態redux
子組件寫法以下:數組
import React,{Component} from 'react'; export default class ComponentSon extends Component{ render(){ console.log('Component rendered : ' + Date.now()); const msg = this.props.sonMsg; return ( <div> {msg}</div> ) } }
不管什麼點擊哪一個事件,都會觸發 ComponentSon 的渲染更新。 控制檯也能夠看到自組件打印出來的信息:性能優化
PureComponent rendered : 1561790880451
可是這並非咱們想要的吧?按理來講,子組件須要用到的那個props更新了,纔會從新渲染更新,這個纔是咱們想要的。還好React 的class組件類中的shouldComponentUpdate能夠解決咱們的問題。數據結構
// shouldComponentUpdate import React,{Component} from 'react'; export default class ComponentSon extends Component{ shouldComponentUpdate(nextProps,nextState){ console.log('當前現有的props值爲'+ this.props.sonMsg); console.log('即將傳入的props值爲'+ nextProps.sonMsg); } render(){ console.log('PureComponent rendered : ' + Date.now()); const msg = this.props.sonMsg; return ( <div> {msg}</div> ) } }
注意,這個shouldComponentUpdate要返回一個boolean值的,這裏我沒有返回,看看控制檯怎麼顯示?當我點擊修改parentMsg的元素時:框架
當前現有的props值爲son 即將傳入的props值爲son Warning: ComponentSon.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.
也就是說,控制檯給出了一個警告,同時shouldComponentUpdate默認返回true,即要更新子組件。所以,避免props沒有發生變化的時候更新,咱們能夠修改shouldComponentUpdate:ide
shouldComponentUpdate(nextProps,nextState){
console.log('當前現有的props值爲'+ this.props.sonMsg); console.log('即將傳入的props值爲'+ nextProps.sonMsg); return this.props.sonMsg !== nextProps.sonMsg }
這樣就解決了沒必要要的更新。函數
在上述例子中,咱們看到了shouldComponentUpdate在 class 組件上的積極做用,是否是每一個class組件都得本身實現一個shouldComponentUpdate判斷呢?react 官方推出的PureComponent就封裝了這個,幫忙解決默認狀況下子組件渲染策略的問題。性能
import React,{PureComponent} from 'react'; export default class ComponentSon extends PureComponent{ render(){ console.log('PureComponent rendered : ' + Date.now()); const msg = this.props.sonMsg; return ( <div> {msg}</div> ) } }
當父組件修改跟子組件無關的狀態時,不再會觸發自組件的更新了。
用PureComponent會不會有什麼缺點呢?
這裏咱們是傳入一個string字符串(基本數據類型)做爲props傳遞給子組件。 這裏咱們是傳入一個object對象(引用類型)做爲props傳遞給子組件。
import React,{Component} from 'react'; import ComponentSon from './components/ComponentSon'; import './App.css'; class App extends Component{ state = { parentMsg:'parent', sonMsg:{ val:'this is val of son' } } render(){ return ( <div className="App"> <header className="App-header" onClick={()=> {this.setState({parentMsg:'parent' + Date.now()})}}> <p> {this.state.parentMsg} </p> </header> <button onClick={()=> this.setState(({sonMsg}) => { sonMsg.val = 'son' + Date.now(); console.table(sonMsg); return {sonMsg} }) }>修改子組件props</button> <ComponentSon sonMsg={this.state.sonMsg}/> </div> );} } export default App;
當咱們點擊button按鈕的時候,觸發了setState,修改了state,可是自組件卻沒有跟新。爲何呢?這是由於:
PureComponent 對狀態的對比是淺比較的
PureComponent 對狀態的對比是淺比較的
PureComponent 對狀態的對比是淺比較的
this.setState(({sonMsg}) => { sonMsg.val = 'son' + Date.now(); console.table(sonMsg); return {sonMsg} })
這個修改state的操做就是淺複製操做。什麼意思呢?這就比如
let obj1 = { val: son } obj2 = obj1; obj2.val = son + '1234'; // obj1 的val 也同時被修改。由於他們指向同一個地方引用 obj1 === obj2;// true
也就是說,咱們修改了新狀態的state,也修改了老狀態的state,二者指向同一個地方。PureComponent 發現你傳入的props 和 此前的props 同樣的,指向同一個引用。固然不會觸發更新了。
那咱們應該怎麼作呢?react 和 redux中也一直強調,state是不可變的,不能直接修改當前狀態,要返回一個新的修改後狀態對象
所以,咱們改爲以下寫法,就能夠返回一個新的對象,新對象跟其餘對象確定是不想等的,因此淺比較就會發現有變化。自組件就會有渲染更新。
this.setState(({sonMsg}) => { console.table(sonMsg); return { sonMsg:{ ...sonMsg, val:'son' + Date.now() } } })
除了上述寫法外,咱們可使用Object.assign來實現。也可使用外部的一些js庫,好比Immutable.js等。
而此前的props是字符串字符串是不可變的(Immutable)。什麼叫不可變呢?就是聲明一個字符串並賦值後,字符串再也無法改變了(針對內存存儲的地方)。
let str = "abc"; // str 不能改變了。 str +='def' // str = abcdef
看上去str 由最開始的 ‘abc’ 變爲‘abcdef’變了,其實是沒有變。 str = ‘abc’的時候,在內存中開闢一塊空間棧存儲了abc,並將str指向這塊內存區域。 而後執行 str +='def'這段代碼的時候,又將結果abcdef存儲到新開闢的棧內存中,再將str指向這裏。所以原來的內存區域的abc一直沒有變化。若是是非全局變量,或沒被引用,就會被系統垃圾回收。
React.PureComponent 中的 shouldComponentUpdate() 僅做對象的淺層比較。若是對象中包含複雜的數據結構,則有可能由於沒法檢查深層的差異,產生錯誤的比對結果。僅在你的 props 和 state 較爲簡單時,才使用 React.PureComponent,或者在深層數據結構發生變化時調用 forceUpdate() 來確保組件被正確地更新。你也能夠考慮使用 immutable 對象加速嵌套數據的比較。
此外,React.PureComponent 中的 shouldComponentUpdate() 將跳過全部子組件樹的 prop 更新。所以,請確保全部子組件也都是「純」的組件。
上述咱們花了很大篇幅,講的都是class組件,可是隨着hooks出來後,更多的組件都會偏向於function 寫法了。React 16.6.0推出的重要功能之一,就是React.memo。
React.memo 爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。
若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在 React.memo 中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。
// 子組件 export default function Son(props){ console.log('MemoSon rendered : ' + Date.now()); return ( <div>{props.val}</div> ) }
上述跟class組件中沒有繼承PureComponent同樣,只要是父組件狀態更新的時候,子組件都會從新渲染。因此咱們用memo來優化:
import React,{memo} from 'react'; const MemoSon = memo(function Son(props){ console.log('MemoSon rendered : ' + Date.now()); return ( <div>{props.val}</div> ) }) export default MemoSon;
默認狀況下其只會對複雜對象作淺層對比,若是你想要控制對比過程,那麼請將自定義的比較函數經過第二個參數傳入來實現。
function MyComponent(props) { /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) { /* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false */ } export default React.memo(MyComponent, areEqual);
注意點以下:
本次咱們討論了react組件渲染的優化方法之一。class組件對應PureComponent,function組件對應memo。也簡單講述了在使用過程當中的注意點。後續開發組件的時候,或許能對性能方面有必定的提高。