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

前言

關於react性能優化,在react 16這個版本,官方推出fiber,在框架層面優化了react性能上面的問題。因爲這個太過於龐大,咱們今天圍繞子自組件更新策略,從兩個及其微小的方面來談react性能優化。 其主要目的就是防止沒必要要的子組件渲染更新css

子組件什麼時候更新?shouldComponentUpdate?

首先咱們看個例子,父組件以下: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能夠解決咱們的問題。bash

// 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:框架

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。也簡單講述了在使用過程當中的注意點。後續開發組件的時候,或許能對性能方面有必定的提高。

相關文章
相關標籤/搜索