详解使用React.memo()来优化函数组件的性能


Posted in Javascript onMarch 19, 2019

React核心开发团队一直都努力地让React变得更快。在React中可以用来优化组件性能的方法大概有以下几种:

  • 组件懒加载(React.lazy(...)和<Suspense />)
  • Pure Component
  • shouldComponentUpdate(...){...}生命周期函数

本文还会介绍React16.6加入的另外一个专门用来优化函数组件(Functional Component)性能的方法: React.memo。

无用的渲染

组件是构成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组件不应该被重新渲染,可是现实是这样的吗?

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

在浏览器中运行我们的代码,然后多次点击Click Me按钮,你可以看到以下输出:

详解使用React.memo()来优化函数组件的性能

我们可以看到'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,这样组件将会重新进行渲染。

再次在浏览器中测试我们的组件,刚开始的界面是这样的:

详解使用React.memo()来优化函数组件的性能

这时候,就算我们多次点击Click Me按钮,也只能看到两行输出:

componentWillUpdate
componentDidUpdate

因为第二次点击Click Me按钮后count值一直是1,这样shouldComponentUpdate一直返回false,所以组件就不再被重新渲染了。

那么如何验证后面state的值发生改变,组件还是会被重新渲染呢?我们可以在浏览器的React DevTools插件中直接对TestC组件的状态进行更改。具体做法是, 在Chrome调试工具中点击React标签,在界面左边选中TestC组件,在界面的右边就可以看到其状态state中只有一个键count,且其值是1:

详解使用React.memo()来优化函数组件的性能

然后让我们点击count的值1,将其修改为2,然后按回车键:

详解使用React.memo()来优化函数组件的性能

你将会看到控制台有以下输出:

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按钮看组件被渲染了多少遍:

详解使用React.memo()来优化函数组件的性能

由上面的输出可知,我们的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} />

当上面的代码初次加载时,控制台的输出是:

详解使用React.memo()来优化函数组件的性能

同样,我们可以打开Chrome的调试工具,点击React标签然后选中TestC组件:

详解使用React.memo()来优化函数组件的性能

我们可以看到这个组件的参数值是5,让我们将这个值改为45, 这时候浏览器输出:

详解使用React.memo()来优化函数组件的性能

由于count的值改变了,所以该组件也被重新渲染了,控制台输出Object{count: 45},让我们重复设置count的值为45, 然后再看一下控制台的输出结果:

详解使用React.memo()来优化函数组件的性能

由输出结果可以看出,即使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,我们将会看到我们的应用被重新渲染了:

详解使用React.memo()来优化函数组件的性能

然后重复设置count的值为89:

详解使用React.memo()来优化函数组件的性能

这里没有重新渲染!

这就是React.memo(...)这个函数牛X的地方!

在我们之前那个没用到React.memo(...)的例子中,count的重复设置会使组件进行重新渲染。可是我们用了React.memo后,该组件在传入的值不变的前提下是不会被重新渲染的。

结论

以下是几点总结:

  • React.PureComponent是银
  • React.memo(...)是金
  • React.PureComponent是给ES6的类组件使用的
  • React.memo(...)是给函数组件使用的
  • React.PureComponent减少ES6的类组件的无用渲染
  • React.memo(...)减少函数组件的无用渲染
  • 为函数组件提供优化是一个巨大的进步

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
mysql输出数据赋给js变量报unterminated string literal错误原因
May 22 Javascript
JS执行删除前的判断代码
Feb 18 Javascript
jQuery制作简单柱状图实例
Jan 28 Javascript
jQuery源码解读之removeClass()方法分析
Feb 20 Javascript
JS数组array元素的添加和删除方法代码实例
Jun 01 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
Jul 28 Javascript
微信小程序获取用户openId的实现方法
May 23 Javascript
详解在AngularJS的controller外部直接获取$scope
Jun 02 Javascript
深入理解Vue transition源码分析
Jul 30 Javascript
React-native桥接Android原生开发详解
Jan 17 Javascript
JS实现的排列组合算法示例
Jul 16 Javascript
vue中使用mockjs配置和使用方式
Apr 06 Vue.js
vue组件定义,全局、局部组件,配合模板及动态组件功能示例
Mar 19 #Javascript
express.js中间件说明详解
Mar 19 #Javascript
js array数组对象操作方法汇总
Mar 18 #Javascript
浅析JavaScript异步代码优化
Mar 18 #Javascript
js实现图片局部放大效果详解
Mar 18 #Javascript
详解在React项目中安装并使用Less(用法总结)
Mar 18 #Javascript
vue动画效果实现方法示例
Mar 18 #Javascript
You might like
PHP CodeBase:将时间显示为&quot;刚刚&quot;&quot;n分钟/小时前&quot;的方法详解
2013/06/06 PHP
PHP和JavaScrip分别获取关联数组的键值示例代码
2013/09/16 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
PHP简单获取及判断提交来源的方法
2016/04/22 PHP
php语法检查的方法总结
2019/01/21 PHP
解决FLASH需要点击激活的代码
2006/12/20 Javascript
使用JS操作页面表格,元素的一些技巧
2007/02/02 Javascript
简单的Jquery遮罩层代码实例
2013/11/14 Javascript
javascript创建和存储cookie示例
2014/01/07 Javascript
jquery制作居中遮罩层效果分享
2014/02/21 Javascript
JS基于cookie实现来宾统计记录访客信息的方法
2015/08/04 Javascript
javascript实现tab响应式切换特效
2016/01/29 Javascript
JSON创建键值对(key是中文或者数字)方式详解
2017/08/24 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
2017/10/23 Javascript
axios进阶实践之利用最优雅的方式写ajax请求
2017/12/20 Javascript
基于ionic实现下拉刷新功能
2018/05/10 Javascript
JS+canvas画布实现炫酷的旋转星空效果示例
2019/02/13 Javascript
layui关闭弹窗后刷新主页面和当前更改项的例子
2019/09/06 Javascript
微信小程序跨页面数据传递事件响应实现过程解析
2019/12/19 Javascript
[43:47]完美世界DOTA2联赛PWL S3 LBZS vs Phoenix 第一场 12.09
2020/12/11 DOTA
python实现数独游戏 java简单实现数独游戏
2018/03/30 Python
Python列表生成式与生成器操作示例
2018/08/01 Python
基于PyQt4和PySide实现输入对话框效果
2019/02/27 Python
Python面向对象总结及类与正则表达式详解
2019/04/18 Python
python实现控制COM口的示例
2019/07/03 Python
pandas如何处理缺失值
2019/07/31 Python
使用python快速在局域网内搭建http传输文件服务的方法
2019/11/14 Python
django ObjectDoesNotExist 和 DoesNotExist的用法
2020/07/09 Python
Python datetime模块的使用示例
2021/02/02 Python
高中毕业生个人自我鉴定
2013/11/24 职场文书
校庆标语集锦
2014/06/25 职场文书
房地产端午节活动方案
2014/08/24 职场文书
三方股东合作协议书范本
2014/09/28 职场文书
井冈山红色之旅心得体会
2014/10/07 职场文书
opencv读取视频并保存图像的方法
2021/06/04 Python
Redis 哨兵集群的实现
2021/06/18 Redis