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

相关文章
相关标签/搜索