Web 性能優化: 使用 React.memo() 提升 React 組件性能

圖片描述

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!前端

這是 Web 性能優化的第四篇,以前的能夠在下面點擊查看:react

  1. Web 性能優化: 使用 Webpack 分離數據的正確方法
  2. Web 性能優化: 圖片優化讓網站大小減小 62%
  3. Web 性能優化: 緩存 React 事件來提升性能

React.js 核心團隊一直在努力使 React 變得更快,就像燃燒的速度同樣。爲了讓開發者可以加速他們的 React 應用程序,爲此增長了不少工具:git

  • Suspense 和 Lazy Load (React.lazy(…), <Suspense/>)
  • 純組件
  • shouldComponentUpdate(…){…} 生命週期鉤子

在這篇文章中,咱們將介紹 React v16.6 中新增的另外一個優化技巧,以幫助加速咱們的函數組件:React.memogithub

提示:使用 Bit 共享和安裝 React 組件。使用你的組件來構建新的應用程序,並與你的團隊共享它們以更快地構建。segmentfault

圖片描述

浪費的渲染

組件構成 React 中的一個視圖單元。這些組件具備狀態,此狀態是組件的本地狀態,當狀態值因用戶操做而更改時,組件知道什麼時候從新渲染。如今,React 組件能夠從新渲染 五、10 到 90次。有時這些從新渲染多是必要的,但大多數狀況下不是必需的,因此這些沒必要要的這將致使咱們的應用程序嚴重減速,下降了性能。數組

來看看如下這個組件:瀏覽器

import React from 'react'

class TestC extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate')
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('componentDidUpdate')
  }

  render () {
    return (
      <div>
        {this.state.count}
        <button onClick={ () => this.setState({count: 1})}>Click Me</button>
      </div>
    )
  }
}

export default TestC;

該組件的初始狀態爲 {count: 0} 。當咱們單擊 click Me 按鈕時,它將 count 狀態設置爲 1。屏幕的 0 就變成了 1。.當咱們再次單擊該按鈕時出現了問題,組件不該該從新呈現,由於狀態沒有更改。count 的上個值爲1,新值也 1,所以不須要更新 DOM。緩存

這裏添加了兩個生命週期方法來檢測當咱們兩次設置相同的狀態時組件 TestC 是否會更新。我添加了componentWillUpdate,當一個組件因爲狀態變化而肯定要更新/從新渲染時,React 會調用這個方法;還添加了componentdidUpdate,當一個組件成功從新渲染時,React 會調用這個方法。性能優化

在瀏覽器中運行咱們的程序,並屢次單擊 Click Me 按鈕,會看到在控制打印不少次信息:函數

圖片描述

在咱們的控制檯中有 「componentWillUpdate」 和 「componentWillUpdate」 日誌,這代表即便狀態相同,咱們的組件也在從新呈現,這稱爲浪費渲染。

純組件/shouldComponentUpdate

爲了不 React 組件中的渲染浪費,咱們將掛鉤到 shouldComponentUpdate 生命週期方法。

shouldComponentUpdate 方法是一個生命週期方法,當 React 渲染 一個組件時,這個方法不會被調用 ,並根據返回值來判斷是否要繼續渲染組件。

假若有如下的 shouldComponentUpdadte:

shouldComponentUpdate (nextProps, nextState) {
  return true
}
  • nextProps: 組件接收的下一個 props 值。
  • nextState: 組件接收的下一個 state 值。

在上面,告訴 React 要渲染咱們的組件,這是由於它返回 true

若是咱們這樣寫:

shouldComponentUpdate(nextProps, nextState) {
   return false
}

咱們告訴 React 永遠不要渲染組件,這是由於它返回 false

所以,不管什麼時候想要渲染組件,都必須返回 true。如今,能夠重寫 TestC 組件:

import React from 'react';
class TestC extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    componentWillUpdate(nextProps, nextState) {
        console.log('componentWillUpdate')
    }
    componentDidUpdate(prevProps, prevState) {
        console.log('componentDidUpdate')
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.state.count === nextState.count) {
            return false
        }
        return true
    }
    render() {
        return ( 
            <div> 
            { this.state.count } 
            <button onClick = {
                () => this.setState({ count: 1 }) }> Click Me </button> 
            </div>
        );
    }
}
export default TestC;

咱們在 TestC 組件中添加了 shouldComponentUpdate,咱們檢查了當前狀態對象this.state.count 中的計數值是否等於 === 到下一個狀態 nextState.count 對象的計數值。 若是它們相等,則不該該從新渲染,所以咱們返回 false,若是它們不相等則返回 true,所以應該從新渲染以顯示新值。

在咱們的瀏覽器中測試,咱們看到咱們的初始渲染:

圖片描述

若是咱們屢次點擊 click Me 按鈕,咱們只會獲得:

componentWillUpdate
componentDidUpdate

圖片描述

咱們能夠從 React DevTools 選項卡中操做 TestC 組件的狀態,單擊 React 選項,選擇右側的 TestC,咱們將看到帶有值的計數狀態:

圖片描述

在這裏,咱們能夠改變數值,點擊count文本,輸入 2,而後回車:

圖片描述

你會看到狀態計數增長到 2,在控制檯會看到:

componentWillUpdate
componentDidUpdate
componentWillUpdate
componentDidUpdate

圖片描述

這是由於上個值爲 1 且新值爲 2,所以須要從新渲染。

如今,使用 純組件

React在v15.5中引入了Pure Components。 這啓用了默認的相等性檢查(更改檢測)。 咱們沒必要將 shouldComponentUpdate 生命週期方法添加到咱們的組件以進行更改檢測,咱們只須要繼承 React.PureComponent,React 將會本身判斷是否須要從新渲染。

注意:

1)繼承 React.PureComponent 時,不能再重寫 shouldComponentUpdate,不然會引起警告

Warning: ListOfWords has a method called shouldComponentUpdate(). shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.

2)繼承PureComponent時,進行的是淺比較,也就是說,若是是引用類型的數據,只會比較是否是同一個地址,而不會比較具體這個地址存的數據是否徹底一致。

class ListOfWords extends React.PureComponent {
 render() {
     return <div>{this.props.words.join(',')}</div>;
 }
}
class WordAdder extends React.Component {
 constructor(props) {
     super(props);
     this.state = {
         words: ['marklar']
     };
     this.handleClick = this.handleClick.bind(this);
 }
 handleClick() {
     // This section is bad style and causes a bug
     const words = this.state.words;
     words.push('marklar');
     this.setState({words: words});
 }
 render() {
     return (
         <div>
             <button onClick={this.handleClick}>click</button>
             <ListOfWords words={this.state.words} />
         </div>
     );
 }
}

上面代碼中,不管你怎麼點擊按鈕,ListOfWords 渲染的結果始終沒變化,緣由就是WordAdderword 的引用地址始終是同一個。

固然若是想讓你變化,只要在 WordAdder 的 handleClick 內部,將 const words = this.state.words; 改成 const words = this.state.words.slice(0),就好了,由於改變了引用地址。

3)淺比較會忽略屬性或狀態突變的狀況,其實也就是,數據引用指針沒變而數據被改變的時候,也不新渲染組件。但其實很大程度上,咱們是但願從新渲染的。因此,這就須要開發者本身保證避免數據突變。

接着讓咱們修改咱們的 TestC 組件來使用 PureComponent:

import React from 'react';
class TestC extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    componentWillUpdate(nextProps, nextState) {
        console.log('componentWillUpdate')
    }
    componentDidUpdate(prevProps, prevState) {
        console.log('componentDidUpdate')
    }
    /*shouldComponentUpdate(nextProps, nextState) {
        if (this.state.count === nextState.count) {
            return false
        }
        return true
    }*/
    render() {
        return ( 
            <div> 
            { this.state.count } 
            <button onClick = {
                () => this.setState({ count: 1 })
            }> Click Me </button> 
            </div >
        );
    }
}
export default TestC;

這裏註釋掉了 shouldComponentUpdate 實現,咱們不須要它,由於 React.PureComponent 將爲咱們作檢查。

試它,從新加載你的瀏覽器,並點擊屢次點擊 Click Me 按鈕:

圖片描述

圖片描述

如今,咱們已經看到如何在 React 中優化類組件中的從新渲染,讓咱們看看咱們如何在函數組件中實現一樣的效果。

函數組件

如今,咱們看到了如何使用 Pure Components 和 shouldComponentUpdate 生命週期方法優化上面的類組件,是的,類組件是 React 的主要組成部分。 可是函數也能夠在 React 中用做爲組件。

function TestC(props) {
  return (
    <div>
      I am a functional component
    </div>
  )
}

這裏的問題是,函數組件沒有像類組件有狀態(儘管它們如今利用Hooks useState的出現使用狀態),並且咱們不能控制函數組件的是否從新渲染,由於咱們不能像在類組件中使用生命週期方法。

若是能夠將生命週期鉤子添加到函數組件,那麼就以添加 shouldComponentUpdate 方法來告訴React 何時從新渲染組件。 固然,在函數組件中,咱們不能使用 extend React.PureComponent 來優化咱們的代碼

讓咱們將 TestC 類組件轉換爲函數組件。

import Readct from 'react';

const TestC = (props) => {
  console.log(`Rendering TestC :` props)
  return (
    <div>
      {props.count}
    </div>
  )
}

export default TestC;

// App.js
<TextC count={5}/>

在第一次渲染時,它將打印 Rendering TestC :5

圖片描述

打開 DevTools 並單擊 React 選項。在這裏,更改 TestC 組件的 count5.

圖片描述

若是咱們更改數字並按回車,組件的 props 將更改成咱們在文本框中輸入的值,接着繼續更爲 45:

圖片描述

移動到 Console 選項

圖片描述

咱們看到 TestC 組件從新渲染,由於上個值爲 5,當前值爲 45.如今,返回 React 選項並將值更改成 45,而後移至 Console:

圖片描述

看到組件從新渲染,且上個值與當前值是同樣的。

咱們如何控制從新渲染?

解決方案:使用 React.memo()

React.memo(...) 是 React v16.6 中引入的新功能。 它與 React.PureComponent 相似,它有助於控制 函數組件 的從新渲染。 React.memo(...) 對應的是函數組件,React.PureComponent 對應的是類組件。

如何使用 React.memo(…)

這很簡單,假設有一個函數組件,以下:

const Funcomponent = ()=> {
    return (
        <div>
            Hiya!! I am a Funtional component
        </div>
    )
}

咱們只需將 FuncComponent 做爲參數傳遞給 React.memo 函數:

const Funcomponent = ()=> {
    return (
        <div>
            Hiya!! I am a Funtional component
        </div>
    )
}
const MemodFuncComponent = React.memo(Funcomponent)

React.memo 會返回了一個純組件 MemodFuncComponent。 咱們將在 JSX 標記中渲染此組件。 每當組件中的 propsstate 發生變化時,React 將檢查 上一個 stateprops 以及下一個 propsstate 是否相等,若是不相等則函數組件將從新渲染,若是它們相等則函數組件將不會從新渲染。

如今來試試 TestC 函數組件:

let TestC = (props) => {
    console.log('Rendering TestC :', props)
    return ( 
        <div>
        { props.count }
        </>
    )
}
TestC = React.memo(TestC);

打開瀏覽器並加載應用程序,打開 DevTools 並單擊 React 選項,選擇 <Memo(TestC)>

如今,若是咱們在右邊編輯 count 值爲到 89,會看到咱們的應用程序從新渲染:

圖片描述

若是咱們在將值改成與上個同樣的值: 89:

圖片描述

不會有從新渲染!!

總結

總結幾個要點:

  • React.PureComponent 是銀
  • React.memo(…) 是金。
  • React.PureComponent 是 ES6 類的組件
  • React.memo(...) 是函數組件
  • React.PureComponent 優化 ES6 類組件中的從新渲染
  • React.memo(...) 優化函數組件中的從新渲染

原文:

https://blog.bitsrc.io/improv...

你的點贊是我持續分享好東西的動力,歡迎點贊!

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索