React性能探索 --- 避免不必要渲染

背景

上一篇文章的结尾 https://cloud.tencent.com/developer/article/1009611 我们说到,也许,不是所有的节点都需要重新渲染,对于那些不需要渲染的节点,我们如何找到它们并做优化呢?

本篇文章来具体解答这个问题。

应用分析

首先,先看这个应用:页面的两部分分别渲染5000个节点,从1-5000。当点击按钮之后,第二部分的节点会更新,重新渲染从2-5001的数字,但是第一部分保持不变。

import React, { createElement, Component } from 'react';
import {render} from 'react-dom';
import Perf from 'react-addons-perf';
import ListItem from './ListItem'

function arrayGenerator(length) {
  return Array.apply(null, { length: length }).map(Number.call, Number)
}


class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      multiplier: 1
    }
  }

  resetMultiplier() {
    this.setState({ multiplier: 2 })
  }

  render() {
    return (
      <div className="App">
        <button onClick={this.resetMultiplier.bind(this)}>Click Me</button>
        <ul>
          {
            arrayGenerator(5000).map(i => {
              return <ListItem key={i} text={i}/>
            })
          }
          {
            arrayGenerator(5000).map(i => {
              return <ListItem key={i} text={i + this.state.multiplier}/>
            })
          }
        </ul>
      </div>
    );
  }
}

render(<App />,document.getElementById('main'));

gitbug 链接: https://github.com/hhhuangqiong/performance-for-react

感兴趣的同学可以下载跑一跑代码

分析更新时间

这里用react的Perf工具来测量重新渲染的时间。

使用方法:

npm install --save-dev react-addons-perf
import Perf from 'react-addons-perf'

这里主要用到四个方法:

  • Perf.start():开始计时
  • Perf.stop():结束计时
  • Perf.printInclusive():打印组件总的渲染时间
  • Perf.printWasted():打印浪费的时间

开始计时的函数,我把它放到resetMultiplier里,即将发生发生改变时开始计时。然后在componentDidUpdate里,用Perf.stop()结束计时,然后打印渲染组件的时间跟浪费的时间。

  componentDidUpdate() {
    Perf.stop()
    Perf.printInclusive()
    Perf.printWasted()
  }

  resetMultiplier() {
    Perf.start()
    this.setState({ multiplier: 2 })
  }

当我们点击按钮,可以看到控制台打印出下面的信息:

由控制台的数据可以看出,App用了90.59ms渲染,其中渲染ListItem的时间为55ms,渲染了10000次,其中有5000次是浪费的,因为这部分页面的内容完全没有更新的改动。

如何修复

既然是不需要渲染,那就要阻止它的渲染。React给我们提供了一个方法shouldComponentUpdate(),当这个方法返回true的时候,需要重新渲染,false的时候不需要(默认是true).

在这个栗子中,只要text的值不变,就不需要重新渲染。所以,可以这样改写ListItem 的shouldComponentUpdate

import React, { Component } from 'react'

export default class ListItem extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.text !== this.props.text  
  }

  render() {
    let { text } = this.props
    return <li>{text}</li>
  }
}

在重新点击一下按钮,在控制台可以发现

App总的渲染时间降到了62.14ms,并且ListItem只重新渲染了5000个节点,完全消除了浪费的渲染。

对于上面的写法,React提供了一个新的组件PureComponent来做这件事,它会自动浅对比props/state,当两者相同的时候不渲染节点。所以,listItem又可以改写成

import React, { PureComponent } from 'react'

export default class ListItem extends PureComponent {
  render() {
    let { text } = this.props
    return <li>{text}</li>
  }
}

跑一跑代码

通过控制台可以看到达到的效果是一样的(有点误差是正常的)。

这里再安利一个可以发现应用里是否存在不该重新渲染的节点工具:why-did-you-update

使用方法

npm i --save-dev why-did-you-update
import React from 'react'
if (process.env.NODE_ENV !== 'production') {
  const {whyDidYouUpdate} = require('why-did-you-update')
  whyDidYouUpdate(React)
}

然后点击按钮看控制台

可以看到不应该重新渲染的节点出现了Value did not change. Avoidable re-render!的警告,是不是很实用!

注意的点

PureComponent只会浅比较,所以不适合用于深层嵌套的对象。同时,PureComponent不仅仅会跳过自己的重新渲染,还会跳过它所有子节点的,所以要注意,用它的时候是最好没有子节点并且不依赖于global state的展示型组件。

与Staleless的关系

不知道有没有人跟我有这样的疑问,无状态组件跟纯净组件有什么不同?这里做一个区分:

无状态组件只是作为一个展示组件,它的好处是:

  • 易复用,易测试
  • 与逻辑处理数据层解耦,一般来说,app里有越多无状态组件越好,这说明逻辑处理都在上层,例如redux 中处理,这样可以在不渲染的前提下,测数据逻辑。

坏处:

  • 没有生命周期,没办法用shouldComponentUpdate阻止重新渲染,这也就是说,它没有帮助我们提高性能的作用,这也是它跟PureComponent最大的不同。

关于如何在实际中使用这两个组件,还要根据具体的实际情况来选择~

总结

综上可以看出,减少不必要的重新渲染对于提升我们的性能有很大的意义。我个人觉得,在实际中,用Perf跟why-did-you-update两个工具已经可以很好帮我们判断哪部分不需要重新渲染,我们可以根据结果做出优化。

遗留点

PureComponent那么好用,但是使用PureComponent是有条件的呀~

由于PureComponent只是做了一个浅比较,所以深层嵌套的对象跟数组都是比不出来的,可能会导致需要渲染的地方没有重新渲染的错误展示。

那么浅比较又是什么呢?下篇文章我们来继续探索

参考链接:

1、https://60devs.com/pure-component-in-react.html

2、https://engineering.musefind.com/how-to-benchmark-react-components-the-quick-and-dirty-guide-f595baf1014c

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SHERlocked93的前端小站

Vue项目预渲染机制引入实践

周末想顺便把已经做好静态页面的webApp项目做一下SEO优化,由于不想写蹩脚的SSR代码,所以准备采用预渲染,本来想着网上有这么多预渲染的文章,随便找个来跟着...

3992
来自专栏IMWeb前端团队

基于vue2.0+vuex+localStorage开发的本地记事本

本文采用vue2.0+vuex+localStorage+sass+webpack,实现一个本地存储的记事本。兼容PC端和移动端。 在线预览地址:DEMO 功能...

2686
来自专栏Java开发

freemarker中使用shiro标签

继承FreeMarkerConfigurer类,重写afterPropertiesSet()方法;

1652
来自专栏Python疯子

Please verify that your device’s clock is properly set, and that your signing certificate is not exp

Please verify that your device’s clock is properly set, and that your signing ce...

701
来自专栏鹅厂优文

小程序入坑指南 | 鹅厂优文

前段时间,手上刚好接手一个小程序的项目,心想之前自学过一段时间的小程序,终于有项目可以练练手了,可惜,万万没想到,加了两个周末的班结果却成了飞机稿...

1.5K11
来自专栏Hongten

一个小巧的HTML编辑器_CLEditor_源码下载

CLEditor是一个开源的jQuery插件提供了一个轻量级的、全功能、跨浏览器、可扩展、

1971
来自专栏Theo Tsao

Ionic3学习笔记(十四)使用 Videogular2 实现视频播放以及遇到的一些问题

videogular2 GitHub 地址:https://github.com/videogular/videogular2

1064
来自专栏我和未来有约会

CaseStudy(showcase)类库篇-用agTweener来实现动画效果

做silvelight也有一段时间了,相册、游戏,刚刚完成的showcase这个小程序算是一个阶段了。这里就以showcase这个项目来做一下CaseStudy...

18510
来自专栏jeremy的技术点滴

前端ReactJS技术介绍

3673
来自专栏自动化测试实战

Flask第36篇——模板项目实战(二)

前面我们利用宏将首页代码进行了第一次优化。如果我们现在还有其他页面,试想一下,首页上面的搜索框

953

扫码关注云+社区