使用React.memo()來優化函數組件的性能

原文連接: Improving Performance in React Functional Component using React.memo
原文做者: Chidume Nnamdi
譯者: 進擊的大蔥
推薦理由: 本文講述了開發React應用時如何使用shouldComponentUpdate生命週期函數以及PureComponent去避免類組件進行無用的重渲染,以及如何使用最新的React.memo API去優化函數組件的性能。

React核心開發團隊一直都努力地讓React變得更快。在React中能夠用來優化組件性能的方法大概有如下幾種:react

  • 組件懶加載(React.lazy(...)和<Suspense />)
  • Pure Component
  • shouldComponentUpdate(...){...}生命週期函數

本文還會介紹React16.6加入的另一個專門用來優化函數組件(Functional Component)性能的方法: React.memosegmentfault

無用的渲染

組件是構成React視圖的一個基本單元。有些組件會有本身本地的狀態(state), 當它們的值因爲用戶的操做而發生改變時,組件就會從新渲染。在一個React應用中,一個組件可能會被頻繁地進行渲染。這些渲染雖然有一小部分是必須的,不過大多數都是無用的,它們的存在會大大下降咱們應用的性能。數組

看下面這個例子:瀏覽器

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;

TestC組件有一個本地狀態count,它的初始值是0(state = {count: 0})。當咱們點擊Click Me按鈕時,count的值被設置爲1。這時候屏幕的數字將會由0變成1。當咱們再次點擊該按鈕時,count的值仍是1, 這時候TestC組件不該該被從新渲染,但是現實是這樣的嗎?ide

爲了測試count重複設置相同的值組件會不會被從新渲染, 我爲TestC組件添加了兩個生命週期函數: componentWillUpdate和componentDidUpdate。componentWillUpdate方法在組件將要被從新渲染時被調用,而componentDidUpdate方法會在組件成功重渲染後被調用。函數

在瀏覽器中運行咱們的代碼,而後屢次點擊Click Me按鈕,你能夠看到如下輸出:工具


咱們能夠看到'componentWillUpdate'和'componentWillUpdate'在每次咱們點擊完按鈕後,都會在控制檯輸出來。因此即便count被設置相同的值,TestC組件仍是會被從新渲染,這些就是所謂的無用渲染。性能

Pure Component/shouldComponentUpdate

爲了不React組件的無用渲染,咱們能夠實現本身的shouldComponentUpdate生命週期函數。測試

當React想要渲染一個組件的時候,它將會調用這個組件的shouldComponentUpdate函數, 這個函數會告訴它是否是真的要渲染這個組件。優化

若是咱們的shouldComponentUpdate函數這樣寫:

shouldComponentUpdate(nextProps, nextState) {
    return true        
}

其中各個參數的含義是:

  • nextProps: 組件將會接收的下一個參數props
  • nextProps: 組件的下一個狀態state

由於咱們的shouldComponentUpdate函數一直返回true,這就告訴React,不管何種狀況都要從新渲染該組件。

但是若是咱們這麼寫:

shouldComponentUpdate(nextProps, nextState) {
    return false
}

由於這個方法的返回值是false,因此React永遠都不會從新渲染咱們的組件。

所以當你想要React從新渲染你的組件的時候,就在這個方法中返回true,不然返回false。如今讓咱們用shouldComponentUpdate重寫以前的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方法,判斷若是如今狀態的count和下一個狀態的count同樣時,咱們返回false,這樣React將不會進行組件的從新渲染,反之,若是它們兩個的值不同,就返回true,這樣組件將會從新進行渲染。

再次在瀏覽器中測試咱們的組件,剛開始的界面是這樣的:

這時候,就算咱們屢次點擊Click Me按鈕,也只能看到兩行輸出:

componentWillUpdate
componentDidUpdate

由於第二次點擊Click Me按鈕後count值一直是1,這樣shouldComponentUpdate一直返回false,因此組件就再也不被從新渲染了。

那麼如何驗證後面state的值發生改變,組件仍是會被從新渲染呢?咱們能夠在瀏覽器的React DevTools插件中直接對TestC組件的狀態進行更改。具體作法是, 在Chrome調試工具中點擊React標籤,在界面左邊選中TestC組件,在界面的右邊就能夠看到其狀態state中只有一個鍵count,且其值是1:


而後讓咱們點擊count的值1,將其修改成2,而後按回車鍵:


你將會看到控制檯有如下輸出:

componentWillUpdate
componentDidUpdate
componentWillUpdate
componentDidUpdate

state的count被改變了,組件也被從新渲染了。

如今讓咱們使用另一種方法PureComponent來對組件進行優化。

React在v15.5的時候引入了Pure Component組件。React在進行組件更新時,若是發現這個組件是一個PureComponent,它會將組件如今的state和props和其下一個state和props進行淺比較,若是它們的值沒有變化,就不會進行更新。要想讓你的組件成爲Pure Component,只須要extends React.PureComponent便可。

讓咱們用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按鈕看組件被渲染了多少遍:


由上面的輸出可知,咱們的component只在state由0變爲1時被從新渲染了,後面都沒有進行渲染。

函數組件

上面咱們探討了如何使用PureComponentshouldComponentUpdate的方法優化類組件的性能。雖然類組件是React應用的主要組成部分,不過函數組件(Functional Component)一樣能夠被做爲React組件使用。

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

對於函數組件,它們沒有諸如state的東西去保存它們本地的狀態(雖然在React Hooks中函數組件可使用useState去使用狀態), 因此咱們不能像在類組件中使用shouldComponentUpdate等生命函數去控制函數組件的重渲染。固然,咱們也不能使用extends React.PureComponent了,由於它壓根就不是一個類。

要探討解決方案,讓咱們先驗證一下函數組件是否是也有和類組件同樣的無用渲染的問題。

首先咱們先將ES6的TestC類轉換爲一個函數組件:

import React from 'react';

const TestC = (props) => {
    console.log(`Rendering TestC :` props)
    return ( 
        <div>
            {props.count}
        </div>
    )
}
export default TestC;
// App.js
<TestC count={5} />

當上面的代碼初次加載時,控制檯的輸出是:


一樣,咱們能夠打開Chrome的調試工具,點擊React標籤而後選中TestC組件:


咱們能夠看到這個組件的參數值是5,讓咱們將這個值改成45, 這時候瀏覽器輸出:


因爲count的值改變了,因此該組件也被從新渲染了,控制檯輸出Object{count: 45},讓咱們重複設置count的值爲45, 而後再看一下控制檯的輸出結果:


由輸出結果能夠看出,即便count的值保持不變,仍是45, 該組件仍是被重渲染了。

既然函數組件也有無用渲染的問題,咱們如何對其進行優化呢?

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

React.memo(...)是React v16.6引進來的新屬性。它的做用和React.PureComponent相似,是用來控制函數組件的從新渲染的。React.memo(...) 其實就是函數組件的React.PureComponent

如何使用React.memo(...)?

React.memo使用起來很是簡單,假設你有如下的函數組件:

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

咱們只需將上面的Funcomponent做爲參數傳入React.memo中:

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

React.memo會返回一個純化(purified)的組件MemoFuncComponent,這個組件將會在JSX標記中渲染出來。當組件的參數props和狀態state發生改變時,React將會檢查前一個狀態和參數是否和下一個狀態和參數是否相同,若是相同,組件將不會被渲染,若是不一樣,組件將會被從新渲染。

如今讓咱們在TestC組件上使用React.memo進行優化:

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

打開瀏覽器從新加載咱們的應用。而後打開Chrome調試工具,點擊React標籤,而後選中<Memo(TestC)>組件。

接着編輯一下props的值,將count改成89,咱們將會看到咱們的應用被從新渲染了:


而後重複設置count的值爲89:


這裏沒有從新渲染!

這就是React.memo(...)這個函數牛X的地方!

在咱們以前那個沒用到React.memo(...)的例子中,count的重複設置會使組件進行從新渲染。但是咱們用了React.memo後,該組件在傳入的值不變的前提下是不會被從新渲染的。

結論

如下是幾點總結:

  • React.PureComponent是銀
  • React.memo(...)是金
  • React.PureComponent是給ES6的類組件使用的
  • React.memo(...)是給函數組件使用的
  • React.PureComponent減小ES6的類組件的無用渲染
  • React.memo(...)減小函數組件的無用渲染
  • 爲函數組件提供優化是一個巨大的進步

我是進擊的大蔥,關注個人公衆號,獲取我分享的最新技術推送!

相關文章
相關標籤/搜索