详解使用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 相关文章推荐
Jquery 常用方法经典总结
Jan 28 Javascript
禁用Tab键JS代码兼容Firefox和IE
Apr 18 Javascript
JavaScript模拟重力状态下抛物运动的方法
Mar 03 Javascript
Javascript BOM学习小结(六)
Nov 26 Javascript
js创建jsonArray传输至后台及后台全面解析
Apr 11 Javascript
AngularJs表单验证实例代码解析
Nov 29 Javascript
jquery结合html实现中英文页面切换
Nov 29 Javascript
jQuery实现右侧抽屉式在线客服功能
Dec 25 jQuery
JavaScript原型对象、构造函数和实例对象功能与用法详解
Aug 04 Javascript
详解微信小程序实现跑马灯效果(附完整代码)
Apr 29 Javascript
详解Vue后台管理系统开发日常总结(组件PageHeader)
Nov 01 Javascript
uniapp开发小程序实现滑动页面控制元素的显示和隐藏效果
Dec 10 Javascript
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
第六节 访问属性和方法 [6]
2006/10/09 PHP
使用 MySQL Date/Time 类型
2008/03/26 PHP
php修改时间格式的代码
2011/05/29 PHP
WordPress中获取页面链接和标题的相关PHP函数用法解析
2015/12/17 PHP
非集成环境的php运行环境(Apache配置、Mysql)搭建安装图文教程
2016/04/12 PHP
PHP5.5.15+Apache2.4.10+MySQL5.6.20配置方法分享
2016/05/06 PHP
PHP基于curl模拟post提交json数据示例
2018/06/22 PHP
PHP下载文件函数与用法示例
2019/09/27 PHP
Laravel使用swoole实现websocket主动消息推送的方法介绍
2019/10/20 PHP
laravel 解决crontab不执行的问题
2019/10/22 PHP
使用jQuery模板来展现json数据的代码
2010/10/22 Javascript
jQuery实用技巧必备(上)
2015/11/02 Javascript
原生JS实现轮播效果+学前端的感受(防止走火入魔)
2016/08/21 Javascript
js实现随机抽选效果、随机抽选红色球效果
2017/01/13 Javascript
详解tween.js 中文使用指南
2018/01/05 Javascript
JavaScript数组,JSON对象实现动态添加、修改、删除功能示例
2018/05/26 Javascript
vue实现自定义多选与单选的答题功能
2018/07/05 Javascript
详解关于Vue版本不匹配问题(Vue packages version mismatch)
2018/09/17 Javascript
elementUI table表格动态合并的示例代码
2019/05/15 Javascript
JS实现的自定义map方法示例
2019/05/17 Javascript
layui.use模块外部使用其内部定义的js封装函数方法
2019/09/16 Javascript
Python之eval()函数危险性浅析
2014/07/03 Python
详解爬虫被封的问题
2019/04/23 Python
python查找重复图片并删除(图片去重)
2019/07/16 Python
tensorflow 环境变量设置方式
2020/02/06 Python
细说CSS3中box属性中的overflow-x属性和overflow-y属性值的效果
2014/07/21 HTML / CSS
基于html5绘制圆形多角图案
2016/04/21 HTML / CSS
美国知名的女性服饰品牌:LOFT(洛芙特)
2016/08/05 全球购物
巴西化妆品商店:Lojas Rede
2019/07/26 全球购物
澳大利亚买卖正宗二手奢侈品交易平台:Luxe.It.Fwd
2019/10/16 全球购物
中文专业毕业生自荐书范文
2014/01/04 职场文书
自动一体化专业求职信
2014/03/15 职场文书
让生命充满爱观后感
2015/06/08 职场文书
《鸟的天堂》教学反思
2016/02/19 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript
JS前端使用canvas实现物体的点选示例
2022/08/05 Javascript