详解使用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 相关文章推荐
对js eval()函数的一些见解
Aug 15 Javascript
浅谈JavaScript的计时器对象
Dec 26 Javascript
详解webpack分包及异步加载套路
Jun 29 Javascript
Vue.js如何实现路由懒加载浅析
Aug 14 Javascript
nginx+vue.js实现前后端分离的示例代码
Feb 12 Javascript
webpack-dev-server自动更新页面方法
Feb 22 Javascript
详解Node.js读写中文内容文件操作
Oct 10 Javascript
JS前端知识点 运算符优先级,URL编码与解码,String,Math,arguments操作整理总结
Jun 27 Javascript
vue实现图片预览组件封装与使用
Jul 13 Javascript
开发Node CLI构建微信小程序脚手架的示例
Mar 27 Javascript
JS实现多功能计算器
Oct 28 Javascript
JavaScript 反射学习技巧
Oct 16 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
Terran兵种介绍
2020/03/14 星际争霸
在DC的漫画和电影中,蝙蝠侠的宿敌,小丑的真名是什么?
2020/04/09 欧美动漫
php学习笔记 php中面向对象三大特性之一[封装性]的应用
2011/06/13 PHP
PHP计算近1年的所有月份
2017/03/13 PHP
Laravel 加载第三方类库的方法
2018/04/20 PHP
javascript 实现父窗口引用弹出窗口的值的脚本
2007/08/07 Javascript
jQuery EasyUI API 中文文档 - Menu菜单
2011/10/03 Javascript
Javascript学习笔记之 函数篇(一) : 函数声明和函数表达式
2014/06/24 Javascript
在JavaScript的正则表达式中使用exec()方法
2015/06/16 Javascript
jquery实现美观的导航菜单鼠标提示特效代码
2015/09/06 Javascript
Vue.js动态组件解析
2016/09/09 Javascript
jQuery查找节点方法完整实例
2016/09/13 Javascript
JS调用某段SQL语句的方法
2016/10/20 Javascript
JavaScript自定义函数实现查找两个字符串最长公共子串的方法
2016/11/24 Javascript
jQuery中用on绑定事件时需注意的事项
2017/03/19 Javascript
浅谈关于axios和session的一些事
2017/07/13 Javascript
js链表操作(实例讲解)
2017/08/29 Javascript
jQuery+ajax实现动态添加表格tr td功能示例
2018/04/23 jQuery
使用jquery模拟a标签的click事件无法实现跳转的解决
2018/12/04 jQuery
详解使用React.memo()来优化函数组件的性能
2019/03/19 Javascript
[02:32]DOTA2亚洲邀请赛 VG战队巡礼
2015/02/03 DOTA
python中列表元素连接方法join用法实例
2015/04/07 Python
wxPython使用系统剪切板的方法
2015/06/16 Python
Python实现爬取需要登录的网站完整示例
2017/08/19 Python
Python中单例模式总结
2018/02/20 Python
利用Python绘制有趣的万圣节南瓜怪效果
2019/10/31 Python
Python class的继承方法代码实例
2020/02/14 Python
基于python检查矩阵计算结果
2020/05/21 Python
Python包和模块的分发详细介绍
2020/06/19 Python
容易被忽略的Python内置类型
2020/09/03 Python
澳大利亚电子产品购物网站:Dick Smith
2017/02/02 全球购物
瑞典时尚耳机品牌:Urbanears
2017/07/26 全球购物
高中毕业生生活的自我评价
2013/12/08 职场文书
民事授权委托书范文
2014/08/02 职场文书
2014年精神文明工作总结
2014/12/23 职场文书
Python+DeOldify实现老照片上色功能
2022/06/21 Python