详解使用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的attr方法来修改onclick值
Jul 07 Javascript
javascript实现复制与粘贴操作实例
Oct 16 Javascript
Jquery 分页插件之Jquery Pagination
Aug 25 Javascript
jQuery链式操作实例分析
Nov 16 Javascript
JavaScript语言精粹经典实例(整理篇)
Jun 07 Javascript
微信小程序实战之上拉(分页加载)效果(2)
Apr 17 Javascript
vue.js的手脚架vue-cli项目搭建的步骤
Aug 30 Javascript
webpack4+react多页面架构的实现
Oct 25 Javascript
vue中选项卡点击切换且能滑动切换功能的实现代码
Nov 25 Javascript
使用VScode 插件debugger for chrome 调试react源码的方法
Sep 13 Javascript
layui将table转化表单显示的方法(即table.render转为表单展示)
Sep 24 Javascript
javascript进阶篇深拷贝实现的四种方式
Jul 07 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
全国FM电台频率大全 - 25 云南省
2020/03/11 无线电
解析php取整的几种方式
2013/06/25 PHP
Yii框架的redis命令使用方法简单示例
2019/10/15 PHP
Yii框架模拟组件调用注入示例
2019/11/11 PHP
用javascript获取当页面上鼠标光标位置和触发事件的对象的代码
2009/12/09 Javascript
在VS2008中使用jQuery智能感应的方法
2010/12/30 Javascript
10个基于浏览器的JavaScript调试工具分享
2013/02/07 Javascript
jquery parent和parents的区别分析
2013/10/02 Javascript
jquery控制display属性为none或block
2014/03/31 Javascript
Javascript 实现图片无缝滚动
2014/12/19 Javascript
jQuery trigger()方法用法介绍
2015/01/13 Javascript
非常酷炫的Bootstrap图片轮播动画
2016/05/27 Javascript
jquery实现ajax加载超时提示的方法
2016/07/23 Javascript
AngularJS创建自定义指令的方法详解
2016/11/03 Javascript
bootstrap suggest搜索建议插件使用详解
2017/03/25 Javascript
jQuery响应滚动条事件功能示例
2017/10/14 jQuery
详细介绍Python语言中的按位运算符
2013/11/26 Python
举例介绍Python中的25个隐藏特性
2015/03/30 Python
Python中time模块和datetime模块的用法示例
2016/02/28 Python
Python对数据进行插值和下采样的方法
2018/07/03 Python
Python多线程原理与用法实例剖析
2019/01/22 Python
Python 实现两个服务器之间文件的上传方法
2019/02/13 Python
python3对拉勾数据进行可视化分析的方法详解
2019/04/03 Python
Keras 使用 Lambda层详解
2020/06/10 Python
HTML5 Canvas鼠标与键盘事件demo示例
2013/07/04 HTML / CSS
巴西补充剂和维生素购物网站:Natue
2019/06/17 全球购物
程序员跳槽必看面试题总结
2013/06/28 面试题
日语专业毕业生求职信
2013/12/04 职场文书
大学生个人实习的自我评价
2014/02/15 职场文书
超市优秀员工获奖感言
2014/08/15 职场文书
群众路线教育实践活动调研报告
2014/11/03 职场文书
2014年置业顾问工作总结
2014/11/17 职场文书
干部个人考察材料
2014/12/24 职场文书
护士自荐信怎么写
2015/03/06 职场文书
2015小学音乐教师个人工作总结
2015/07/21 职场文书
2016年学校综治宣传月活动总结
2016/03/16 职场文书