如何利用工具提高 React 页面渲染性能之 Perf

前言

用 React 一段时间了,也做了不少列表页。在用 React 做无限下拉加载的列表页时发现个问题:页面前几页渲染速度还挺快的,但是越往下拉加载内容页面的渲染就越慢。这是怎么回事呢?
让我们先来看下 React 的组件渲染流程吧。

React 的组件渲染流程

React 的组件渲染分为初始化渲染和更新渲染。在初始化时,React 会调用根组件下所有组件的 render 方法进行渲染。

在每个生命周期更新时,React 会先调用 shouldComponentUpdate(nextProps, nextState) 方法来判断该组件是否需要更新。该方法会返回 true 或 false 来表示更新或不需要更新。如果不需要更新,则直接保持不变;如果需要更新,则调用 render 方法生成新的虚拟 DOM,然后再用 diff 算法与旧的虚拟 DOM 进行对比,如果结果一致就不更新;如果对比不同,则根据最小粒度改变去更新 DOM。
整个过程如下图所示。

ShouldComponentUpdate 在默认情况下返回的是 true。也就是说 React 默认会调用所有组件的 render 方法生成虚拟 DOM,然后再与旧虚拟 DOM 比较以确定最终组件是否需要更新。这个 render 和 diff 对比的过程对于只是兄弟组件发生了改变,而本身并没有变化的组件来说,很明显存在资源浪费。

那么如何能直观的知道这些浪费都发生在哪些过程中呢?这就轮到 Perf 出场了。

接下来让我们先来了解下什么是 Perf,再看看它都能做些什么。

什么是 Perf ,它能做些什么?

Perf 是 react 官方提供的性能分析工具,可以对我们的应用进行整体性能分析并提供性能数据。
直接来看下具体有哪些 API 吧:

  • Perf.start():开始测量。
  • Perf.stop():停止测量。
  • Perf.getLastMeasurements():在停止测量之后调用,用来获取 measurements。

接下来就可以打印出性能数据了:

  • Perf.printInclusive(measurements):打印出所花费的整体时间。
  • Perf.printExclusive(measurements):打印出处理 props、getInitialState、调用 componentWillMount 和 componentDidMount 等的时间,这里面不包含 mount 组件的时间。
  • Perf.printWasted(measurements):打印出测量时段内所浪费的时间。这部分信息是分析数据中最有用的一部分了。我们可以通过这个数据找出时间被浪费在了哪儿。浪费一般出现在组件没有渲染任何东西的时候,如上文中提到的,组件在 render 出新的虚拟 DOM 和旧的虚拟 DOM 对比之后,发现不需要更新组件。最理想的情况这个的返回值是一个空数组。
  • Perf.printOperations(measurements):打印出分析时段内发生的底层 DOM 操作。

目前不少 React 性能优化的文档里都有提到可以通过 shouldComponenentUpdate 和 Perf 来进行优化,但是却没有进行详细的说明。一开始的时候我是困惑的:

  • Perf 是怎么跑起来的?
  • 在什么时候执行比较好?
  • 性能报表中的各个指标是什么意思呢?
  • 怎么结合这些数据来进行优化?

翻了不少文档并实践之后,以我们到家业务的店铺列表组件的优化为例,总结出来了以下的使用步骤,仅供大家参考。

Perf 怎么用?

使用步骤:

步骤一:获取

  • 先在页面把原来的 react.js 替换成带组件的版本 react-with-addons.js

    • 这里要补充说明下关于使用的 react-with-addons 的版本

      • 推荐使用最新版本 15.3.2。
      • 如果使用 15.1.0 版本,react-with-addons 有可能会出现 Warning: There is an internal error in the React performance measurement code. We did not expect componentWillMount timer to stop while no timer is still in progress for another instance. Please report this as a bug in React。另外会出现有时候执行 React.addons.Perf.printOperations(measurements); 打印不出信息来等一些奇怪的问题。
    • Perf 是在 0.11.0 版本 中新增的, 然后在 [15.1.0 版本]中进行了重构,并在后续版本中修复了不少 bug,目前还在逐渐完善的过程中。另外要注意的是:在非生产环境是不能使用 Perf 的。

步骤二:调用

方式一:直接在浏览器里调用

  1. 在浏览器的控制台里输入:
  React.addons.Perf.start(); 
  1. 执行某个操作,如滚动屏幕来加载列表
  2. 然后在控制台里输入如下代码(以 printWasted 为例):
React.addons.Perf.stop();
var measurements = React.addons.Perf.getLastMeasurements();
React.addons.Perf.printWasted(measurements);

这样就能够看到打印出来这一过程所浪费的时间了。

方式二:添加到组件代码中

在组件的 componentDidUpdate 方法中调用,这样可以在组件每次发生更新时打印出各个性能数据。

componentWillMount() {
  React.addons.Perf.start();
  // Your code
}
componentDidUpdate() {
  // Your code.
  let Perf = React.addons.Perf;
  Perf.stop();

  let measurements = React.addons.Perf.getLastMeasurements();
  if (measurements.length > 0) {
    Perf.printInclusive(measurements);
    Perf.printExclusive(measurements);
    Perf.printWasted(measurements);
    Perf.printOperations(measurements);

    Perf.start(); // clears measurements and try it again
  }
}

这样就可以在页面连续滚动时打印出多个数据。

接下来让我们看下在这些数据中可以发现什么。

数据指标分析

店铺列表在每次下拉刷新时,先变更列表加载状态,再渲染出列表内容。以从第11页下拉翻到第12页为例,我们先来看下优化前后的效果对比图,如下:
优化前:

  • Perf.printInclusive(measurements)

  • Perf.printExclusive(measurements);

  • Perf.printWasted(measurements)

优化后:

  • Perf.printInclusive(measurements)


  • Perf.printExclusive(measurements);


  • Perf.printWasted(measurements)


从上述图表中可以看到,优化之后整体的渲染时间较时间有较大减少,且浪费时间的时间也大幅减少,在执行过程中,有个生命周期中的浪费时间已经减为0了。

下面就来看看这个优化是怎么做的吧。

优化方案

拆分组件,结合 shouldComponentUpdate,以减少重绘次数。

  • 对于静态组件,shouldComponentUpdate 返回 false;
  • 对于组件存在变化的情况
    • 如果变化的 props 或 state 不多,且层次不深,则可以在 shouldComponentUpdate(nextProps, nextState) 里比较新老 props 和 state,在目标 props 或 state 发生变化时 return ture,其余情况都 return false。
    • 如果变化的 props 和 state 多,或者层次深,则最好把组件拆分成变化的和不变化的部分。

注意:这里必须要先确保组件是静态的,即在 componentDidMount 后不会有任何变化,否则不能直接 return false。
在店铺列表组件优化的过程中,一开始没有留意到 ShopCard 组件中的优惠区域高度是会根据优惠条数的不同而有所不同的,并且具有收起和展开的功能,直接 return false 后导致这个区块撑开的高度有问题了,并且收起/展开的功能也失效了。

  • 改出问题的样子:

  • 正常情况初始时的样子:

  • 正常情况展开后的样子:

就拿 ShopCard 组件的代码作为例子看下 shouldComponentUpdate 是怎么样的吧:

  shouldComponentUpdate(nextProps, nextState) {
    let { shouldShowMoreActivities, height } = this.state;

    return shouldShowMoreActivities && height !== nextState.height;
  }

因为这个组件只会受是否有优惠活动和优惠撑开后的高度所影响,所以只要关注 shouldShowMoreActivities 和 height 这两个 state 即可。

修改后整体效果如下:

  • Perf.printInclusive(measurements)

  • Perf.printExclusive(measurements);

  • Perf.printWasted(measurements)

从优化后的效果图中可以看到 ShopCard 组件只渲染了最后一页增加的7项,另外,render time、render count 都从原来的上百减至几个了,且浪费的时间也从原来的几十毫秒减为个位数了。效果还是比较明显的。
但是如果每个组件都要手动覆盖 shouldComponentUpdate 方法也是比较费时的事情,并且这个方法的重写也需要谨慎,可能会带来意想不到的问题。
接下来让我们看下 React 有没有为这个事情做点什么吧。

PureRenderMixin

如果你的组件在相同输入的时候都能够有相同的产出,那么就可以使用 React 提供的 PureRenderMixin 插件,它会自行为组件绑定 shouldComponentUpdate 方法,对现有的子组件的 state 和 props 进行判断。但是它只支持基本类型的浅度比较,如果组件的 props 和 state 数据结构层次复杂则不适用。使用方法如下:

class Shop extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = React.addons.PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    // Your code
  }
}

说明:如果页面上引入的是 react.js,可以自行安装 react-addons-pure-render-mixin 依赖后以如下方式引入:

import PureRenderMixin from 'react-addons-pure-render-mixin';

效果如下图(以 Perf.printWasted(measurements) 为例):

相对于最初版本的已经少了很多,不过比自己实现 shouldComponentUpdate 还是多浪费了 ShopCard 的 15 次 render。

React.PureComponent

在 react 的最新版本里面,还提供了 React.PureComponent 的基础类,直接把原来的 React.Component 替换成 React.PureComponent 即可。
效果如下图(以 Perf.printWasted(measurements)
为例):

效果和使用 PureRenderMixin 差不多。只是需要注意的是 PureComponent 是在 15.3.0 版本中才开始支持的。

另外,Facebook 还提供了一个专门处理不可变数据的库 immutable.js ,大家感兴趣的可自行了解。

清理组件之间不关联的 props 映射

当父组件包含多个子组件,子组件之间存在交互的情况下,有些场景里父组件只是受子组件的某一个属性影响,或者一个子组件只受另外子组件的某些属性影响,那么在 mapStateToProps 的时候就要在各自的 Container 里面把受影响组件的那几个相关 state 映射到 props 里。
但是组件一多,属性一多,这就是件很费神的事情,尤其是写的过程中发现要增加 state 了,就要在关联组件的 mapStateToProps 中挨个加一遍,有时候发现某个属性用不到了又要挨个删一遍。不知道大家有没有这种体验,还是我的使用姿势不对?反正每当这种时候我就特别想把要用到的状态所属的组件定义的整个 state 对象塞到自己的 props 里,这样不管后面加多少 state,也不用再加一遍,而是直接拿这个对象的属性就好了。
但是,这样会有一个副作用,某组件只要一个属性更新了,映射了该组件所属 state 到自己的 props 里的组件就会触发重新渲染了。而如上所说,shouldComponentUpdate 和 PureComponent 适用场景有限。因此,在代码层面能做的优化还是直接做掉吧,而且梳理一遍 props 和 state,可以对组件之间的交互逻辑更了解。
在简化了 props 后,自己编写 shouldComponentUpdate 也会简单很多。

效果如下图(以 Perf.printWasted(measurements) 为例):

从结果中可以看到少了很多浪费时间的项目。

React 页面的性能优化方案还有很多,如合并 setState,合并 dispatch,渐进式渲染等,key,这里就先不一一展开了,后续再讲。

小结

本文主要讲述了如何使用 Perf 性能分析工具结合 React 提供的 shouldComponentUpdate 方法、PureRenderMixin 插件 和 PureComponent 组件来提高 React 组件的渲染性能。
还有其他很多工具如 Chrome 的 Timeline 和 Profiles 也能够帮助我们发现代码中的问题。工具在很大程度上能够给我们带来效率上提升。
但在使用工具的同时,我们也要提高自己代码的质量,合理添加注释,及时清理垃圾代码,优化代码,这样不管是代码执行效率,还是后续的维护都能更高效。

时间: 2024-12-03 13:36:41

如何利用工具提高 React 页面渲染性能之 Perf的相关文章

提高Web页面的性能

提示:您可以先修改部分代码再运行 1. 尽可能的减少 HTTP 的请求数 [content] 2. 使用 CDN(Content Delivery Network) [server] 3. 添加 Expires 头(或者 Cache-control ) [server] 4. Gzip 组件  [server] 5. 将 CSS 样式放在页面的上方 [css] 6. 将脚本移动到底部(包括内联的) [javascript] 7. 避免使用 CSS 中的 Expressions  [css] 8.

提高Web页面的性能(二)_网站应用

1. 尽早清除缓冲区 [server]  2. AJAX 请求使用"GET"方法 [server]  3. 延迟加载组件 [content]  4. 预加载组件 [content]  5. 减少 DOM 元素的数量 [content]  6. 跨域分离组件 [content]  7. 减少 iframes 的数量 [content]  8. 不出现 404 [content]  9. 减小 cookie 的体积 [cookie]  10. 为组件使用 cookie-free 的域名 [

CSS3 will-change提高页面滚动、动画等渲染性能

一.先来看一个例子 下面这个例子来自某外文,我这里简单转述下. 视差滚动现在不是挺流行的嘛,然后Chris Ruppel当其使用background-attachment: fixed实现背景图片不随滚动条滚动而滚动效果的时候,发现,页面的绘制性能掉到了每秒30帧,这种帧频人眼已经可以感觉到一定的顿挫感了. 后来,参考一些其他同事还是同行的建议,做了一番优化,然后,页面的渲染性能-- 这优化之前完全就是便秘,屎拉不出来的感觉:而优化之后,完全就是一泻千里,裤子都来不及脱的感觉. 一兄得便秘,在厕

提高 DHTML 页面性能_javascript技巧

摘要:本文说明了某些 DHTML 功能对性能的重大影响,并提供了一些提高 DHTML 页面性能的技巧. 目录 简介 成批处理 DHTML 更改 使用 innerText 使用 DOM 添加单个元素 扩展 SELECT 元素中的选项 用 DOM 更新表 编写一次,使用多次 请勿过多使用动态属性 数据绑定很有效 不要在 document 对象中设置 expando 属性 避免切换类和样式规则 查找父项之前,先折叠文本范围 其他资料 简介 动态 HTML (DHTML) 在 Microsoft Int

CSS提高渲染性能实现方法

  写CSS的习惯,决定页面渲染速度的快慢,这一点在脑残的IE里更加明显.养成良好的习惯,乃至形成规范,会让你的页面更快速的加载,用户体验度更高,下面是零度逍遥总结的一些提高CSS渲染速度的写法,供大家参考. 1.不要使用*{} 经常有前端开发人员使用*{margin:0; padding:0}来进行CSS重置,这种方法虽然写起来简单,但是渲染起来浏览器引擎要遍历所有的标签,很影响效率,强烈建议不要这样使用! 建议的的解决办法:把你常用到的这些标签进行处理;例如:body,li,p,h1{mar

如何利用 Visual Studio 自带工具提高开发效率

原文:如何利用 Visual Studio 自带工具提高开发效率 Visual Stuido 是一款强大的Windows 平台集成开发工具,你是否好好地利用了它呢?   显示行号 有些时候(比如错误定位)的时候,显示行号将有利于我们进行快速定位.   如何显示 1. 工具 / 选项 / 文本编辑器 -> 选择对应的语言 2. 勾选 "行号"       使用书签 和平常意义的书签类似,当我们希望在日后某一时刻快速定位到一处代码时使用.比如在项目例会上,你需要演示本周你所做的一些改

CSS提高渲染性能具体的实现方法

写css的习惯,决定页面渲染速度的快慢,这一点在脑残的ie里更加明显.养成良好的习惯,乃至形成规范,会让你的页面更快速的加载,用户体验度更高,下面是零度逍遥总结的一些提高css渲染速度的写法,供大家参考. 1.不要使用*{} 经常有前端开发人员使用*{margin:0; padding:0}来进行css重置,这种方法虽然写起来简单,但是渲染起来浏览器引擎要遍历所有的标签,很影响效率,强烈建议不要这样使用! 建议的的解决办法:把你常用到的这些标签进行处理;例如:body,li,p,h1{margi

提高.net页面性能10种方法

1.采用 http module 控制页面的生命周期. 2.自定义response.filter得到输出流stream生成动态页面的静态内容(磁盘缓存). 3.页面gzip压缩. 4.outputcache 编程方式输出页面缓存. 5.删除页面空白字符串.(类似google) 6.完全删除viewstate. 7.删除服务器控件生成的垃圾namingcontainer. 8.使用计划任务按时生成页面.(本文不包含该做法的实现) 9.js,css教程压缩.合并.缓存,图片缓存.(限于文章篇幅,本文

高性能滚动scroll及页面渲染优化

最近在研究页面渲染及web动画的性能问题,以及拜读<CSS SECRET>(CSS揭秘)这本大作. 本文主要想谈谈页面优化之滚动优化. 主要内容包括了为何需要优化滚动事件,滚动与页面渲染的关系,节流与防抖,pointer-events:none 优化滚动.因为本文涉及了很多很多基础,可以对照上面的知识点,选择性跳到相应地方阅读. 滚动优化的由来 滚动优化其实也不仅仅指滚动(scroll 事件),还包括了例如 resize 这类会频繁触发的事件.简单的看看: var i = 0;    wind