[译]使用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.memo浏览器

无用的渲染

组件是构成React视图的一个基本单元。有些组件会有本身本地的状态(state), 当它们的值因为用户的操做而发生改变时,组件就会从新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大下降咱们应用的性能。bash

看下面这个例子:函数

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组件不该该被从新渲染,但是现实是这样的吗?工具

为了测试count重复设置相同的值组件会不会被从新渲染, 我为TestC组件添加了两个生命周期函数: componentWillUpdate和componentDidUpdate。componentWillUpdate方法在组件将要被从新渲染时被调用,而componentDidUpdate方法会在组件成功重渲染后被调用。性能

在浏览器中运行咱们的代码,而后屡次点击Click Me按钮,你能够看到如下输出:测试

咱们能够看到'componentWillUpdate'和'componentWillUpdate'在每次咱们点击完按钮后,都会在控制台输出来。因此即便count被设置相同的值,TestC组件仍是会被从新渲染,这些就是所谓的无用渲染。

Pure Component/shouldComponentUpdate

为了不React组件的无用渲染,咱们能够实现本身的shouldComponentUpdate生命周期函数。优化

当React想要渲染一个组件的时候,它将会调用这个组件的shouldComponentUpdate函数, 这个函数会告诉它是否是真的要渲染这个组件。ui

若是咱们的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(...)减小函数组件的无用渲染
  • 为函数组件提供优化是一个巨大的进步

我是进击的大葱,关注个人公众号,获取我分享的最新技术推送!

相关文章
相关标签/搜索