React效能優化:PureComponent的使用原則

Staticy發表於2019-03-25

首先歡迎大家關注我的Github部落格,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關注呀!react的部分持續更新中...

React15.3中新加了一個PureComponent類,取代之前的PureRenderMixin , PureComponent可以進行React效能優化,減少不必要的render渲染次數,使用時只要把繼承類從Component換成PureComponent。

PureComponent的原理是繼承了Component類,自動載入shouldComponentUpdate函式,當元件更新時,shouldComponentUpdate對props和state進行了一層淺比較,如果元件的props和state都沒有發生改變,render方法就不會觸發,省去Virtual DOM的生成和對比過程,達到提升效能的目的。

下面從PureComponent的使用和原始碼來徹底瞭解它.

shouldComponentUpdate優化效能

在PureComponent之前,我們經常看到優化react效能最常見的手段之一就是在react的生命週期函式shouldComponentUpdate裡判斷props或state的資料是否發生變化,通過返回ture(更新)和false(不更新)來阻止不必要的render.

首先來看看react產生不必要渲染的一個場景:

用create-react-app來初始化react的執行環境,然後在App.js檔案裡建立一個定時任務,每隔一秒更新資料:

import React, { Component } from 'react'
import ShouldComponentUpdateList from './ShouldComponentUpdateList'

// 容器元件
export default class App extends Component {
    state = {
        data: []
    }
    componentDidMount() {
        // 定時任務,每隔一秒更新資料
        setInterval(() => {
            this.setState({
                data: [
                    { title: 'react line 1' }, 
                    { title: 'react line 2' }, 
                ]
            })
        }, 1000)
    }
    render() {
        return(
            <div>
                {
                    this.state.data.map((item, index) => (
                        <ShouldComponentUpdateList key={index} list={item} />
                    ))
                }
            </div>
        )
    }
}
複製程式碼

ShouldComponentUpdateList元件內容為:

export default class List extends Component {
    render() {
        console.log('list render')
        return(
            <div>{this.props.list.title}</div>
        )
    }
}
複製程式碼

命令列執行npm start,瀏覽器檢視輸出:

React效能優化:PureComponent的使用原則

發現控制檯每隔一秒都會輸出list render,明明資料沒有發生變化,但是react還是發生渲染,造成了不必要的渲染浪費。

只需要在shuoldComponentUpdate里加上判斷,再次檢視輸出結果,定時任務的資料沒有發生改變,不會再渲染render函式:

export default class List extends Component {
    // 在shuoldComponentUpdate裡判斷props傳遞的資料沒有發生變化,則不需要render
    shouldComponentUpdate(nextProps) {
        // 返回值為true則render,為false則不render
        if(nextProps.list.title === this.props.list.title) {
            return false
        }
        return true
    }
    render() {
        console.log('list render')
        return(
            <div>{this.props.list.title}</div>
        )
    }
}
複製程式碼

PureComponent使用

除了使用shouldComponentUpdate來判斷是否需要更新元件,還可以用PureComponent, PureComponent實際上自動載入shouldComponentUpdate函式,當元件更新時,shouldComponentUpdate對props和state進行了一層淺比較.

新建PureComponentList元件,用PureComponent代替Component:

import React, { PureComponent } from 'react'
export default class List extends PureComponent {
    render() {
        console.log('list render')
        return(
            <div>{this.props.list.title}</div>
        )
    }
}
複製程式碼

在App元件中傳入:

this.state.data.map((item, index) => (
    <PureComponentList key={index} list={item}/>
))
複製程式碼

然後檢視瀏覽器輸出結果,驚奇地發生,PureComponent並沒有阻止不必要render,這是為什麼呢?因為前面我們說到PureComponent的shouldComponentUpdate只對props和state進行淺比較,也就是this.props = { list: { title: 'react line1' } }nextProps = { list: { title: 'react line1' } },作淺比較的話this.props當然不等於next.props.

為了更清晰地找到原因,我們先來看看PureComponent的原始碼.

PureComponent原始碼

首先找到PureComponent這個函式,在建構函式和原型上分別繼承了Component的屬性和方法:

export default function PureComponent(props, context) {
    Component.call(this, props, context)
}

PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.shouldComponentUpdate = shallowCompare

function shallowCompare(nexProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shollowEqual(this.state, nextState)
}
複製程式碼

接著PureComponent在生命週期函式裡面寫了shallowCompare方法,shallowCompare裡面通過shallowEqual的返回值來返回ture還是false.

接著來看看shallowEqual函式,原始碼地址

export default function shallEqual(objA, objB) {
    // 從後面程式碼可以看出,對於兩個物件的比較為這裡的程式碼
    if (objA === objB) {
        return true;
    }

    if (typeof objA !== 'object' || objA === null ||
        typeof objB !== 'object' || objB === null) {
        return false;
    }

    const keysA = Object.keys(objA);
    const keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
        return false;
    }

    // Test for A's keys different from B.
    for (let i = 0; i < keysA.length; i++) {
        if (!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
            return false;
        }
    }

    return true;
}
複製程式碼

從上面淺比較的原始碼shallowEqual函式可以看出,shallEqual對於物件的比較僅僅通過if (objA === objB) { return true; }來判斷,而let a = { list: { title: 'react line1' } }let b = { list: { title: 'react line1' } }, a === b值為false,所以這就很好的解釋了上面PureComponent並沒有阻止不必要render的原因。

所以我們來改進程式碼,使得PureComponent的props如果傳入物件情況下應該如何起效:

在App.js裡面修改PureComponentList元件傳入item.title而不是item物件,瀏覽器只輸出兩次log:

this.state.data.map((item, index) => (
    // <PureComponentList key={index} list={item}/>
    <PureComponentList key={index} title={item.title}/>
))
複製程式碼

通過解構item物件,傳入item.title,這樣就可以進行淺比較,來達到優化不必要渲染的目的.

PureComponent原則

由上面探究PureComponent原始碼我們知道,PureComponent的元件在props或者state的屬性值是物件的情況下,並不能阻止不必要的渲染,是因為自動載入的shouldComponentUpdate裡面做的只是淺比較,所以想要用PureComponent的特性,應該遵守原則:

  • 確保資料型別是值型別
  • 如果是引用型別,不應當有深層次的資料變化(解構).

React.memo

在使用PureComponent的時候,只能把react元件寫成是class的形式,不能使用函式的形式;react v16.6.0之後,可以使用React.memo來實現函式式的元件,也有了PureComponent的功能。

List元件的PureComponent:

const ListComponent = React.memo(() => (
    <div>{this.props.data || 'loading'}</div>
))
複製程式碼

注:上面涉及到的所有:point_right:原始碼

相關文章