React性能優化之PureComponent 和 memo使用分析

前言

關於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 } 

這樣就解決了沒必要要的更新。函數

PureComponent 更新原理(class 組件)

在上述例子中,咱們看到了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一直沒有變化。若是是非全局變量,或沒被引用,就會被系統垃圾回收。

forceUpdate

React.PureComponent 中的 shouldComponentUpdate() 僅做對象的淺層比較。若是對象中包含複雜的數據結構,則有可能由於沒法檢查深層的差異,產生錯誤的比對結果。僅在你的 props 和 state 較爲簡單時,才使用 React.PureComponent,或者在深層數據結構發生變化時調用 forceUpdate() 來確保組件被正確地更新。你也能夠考慮使用 immutable 對象加速嵌套數據的比較。

此外,React.PureComponent 中的 shouldComponentUpdate() 將跳過全部子組件樹的 prop 更新。所以,請確保全部子組件也都是「純」的組件。

memo

上述咱們花了很大篇幅,講的都是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); 

注意點以下:

  • 此方法僅做爲性能優化的方式而存在。但請不要依賴它來「阻止」渲染,由於這會產生 bug。
  • 與 class 組件中 shouldComponentUpdate() 方法不一樣的是,若是 props 相等,areEqual 會返回 true;若是 props 不相等,則返回 false。這與 shouldComponentUpdate 方法的返回值相反。

結語

本次咱們討論了react組件渲染的優化方法之一。class組件對應PureComponent,function組件對應memo。也簡單講述了在使用過程當中的注意點。後續開發組件的時候,或許能對性能方面有必定的提高。

相關文章
相關標籤/搜索