React性能优化思路

软件的性能优化思路就像生活中去看病,大致是这样的:

  1. 使用工具来分析性能瓶颈(找病根)

  2. 尝试使用优化技巧解决这些问题(服药)

  3. 使用工具测试性能是否确实有提升(疗效确认)

React性能优化的特殊性

看过《高性能JavaScript》这本书的小伙伴都知道,JavaScipt的语言特性、数据结构和算法、浏览器机理、网络传输等都可能导致性能问题。同样是web实现,跟传统的技术(如原生js、jQuery)相比, react的性能优化有什么不同呢?

使用jQuery时,要考虑怎么使用选择器来提高元素查找效率、不要在循环体内进行DOM操作、使用事件委托呀等等。到了React这里,这些东西好像都用不上了。是的,因为React有一个很大的不同点,它实现了虚拟DOM,并且接管了DOM的操作。你不能直接去操作DOM来改变UI,你只能通过改变数据源(props和state)来驱动UI的变化。

说起React的性能分析,还得从它的生命周期和渲染机制说起:

React组件生命周期

当 props 和 state 发生变化时,React会根据shouldComponentUpdate方法来决定是否重新渲染整个组件。

React组件树渲染机制

父亲组件的props 和 state发生变化时,它和它的子组件、孙子组件等所有后代组件都会重新渲染。


综上所述,可以得出React的性能优化就是围绕shouldComponentUpdate方法(SCU)来进行的,无外乎两点:

  1. 缩短SCU方法的执行时间(或者不执行)。

  2. 没必要的渲染,SCU应该返回false。

React 性能分析工具

Web通用工具:Chrome DevTools

最常用到的是Chrome DevTools的Timeline和Profiles。

  • Timeline工具栏提供了对于在装载你的Web应用的过程中,时间花费情况的概览,这些应用包括处理DOM事件, 页面布局渲染或者向屏幕绘制元素。

  • 通过Timeline发现是脚本问题时,使用Profiles作进一步分析。Profiles可以提供更加详细的脚本信息。

React特色工具:Perf

Perf 是react官方提供的性能分析工具。Perf最核心的方法莫过于Perf.printWasted(measurements)了,该方法会列出那些没必要的组件渲染。很大程度上,React的性能优化就是干掉这些无谓的渲染。

有童鞋开发了Chrome扩展程序“React Perf”(戳这里)。相比自己在代码中插入Perf方法进行分析,这个小工具更加灵活方便,墙裂推荐!

案例分析:TodoList

TodoList的功能很简单,就是对待办事项进行增加和删除操作:

import React, {PropTypes, Component} from 'react';class TodoItem extends Component {static propTypes = {deleteItem: PropTypes.func.isRequired,item: PropTypes.shape({text: PropTypes.string.isRequired,id: PropTypes.number.isRequired,}).isRequired,};deleteItem = ()=>{let id = this.props.item.id;this.props.deleteItem(id);};render() {return (<div><button style={{width: 30}} onClick={this.deleteItem}>X</button>&nbsp;<span>{this.props.item.text}</span></div>);}}class Todos extends Component {// 构造constructor(props) {super(props);// 初始状态this.state = {items: this.props.initialItems,text: '',};}static propTypes = {initialItems: PropTypes.arrayOf(PropTypes.shape({text: PropTypes.string.isRequired,id: PropTypes.number.isRequired,}).isRequired).isRequired,};addTask = (e)=> {e.preventDefault();this.setState({items: [{id: ID++, text: this.state.text}].concat(this.state.items),text: '',});};deleteItem = (itemId)=> {this.setState({items: this.state.items.filter((item) => item.id !== itemId),});};render() {return (<div><h1>待办事项</h1><form onSubmit={this.addTask}><input value={this.state.text} onChange={(v)=>{this.setState({text:v.target.value});}}/><button>添加</button></form>{this.state.items.map((item) => {return (<TodoItem key={item.id}item={item}deleteItem={this.deleteItem}/>);})}</div>);}
}let ID = 0;
const items = [];
for (let i = 0; i < 1000; i++) {items.push({id: ID++, text: '事项' + i});
}class TodoList extends Component {render() {return (<Todos initialItems={items}/>);}
}export default TodoList;

在待办事项输入框里输入一个字母,接下来我们以这个行为为例来进行性能分析和优化。

第一次优化

使用Chrome开发者工具的Timeline记录下这个过程:

重点关注出现的红色块,代表这个行为存在性能问题。从上图我们可以看出,耗时的Event(keypress)长条花了98.8ms,其中98.5ms用于脚本处理,可见脚本问题是罪魁祸首。

接着,我们使用Profiles来进一步分析脚本问题:

对Total Time进行降序排列,发现耗时最长的是dispatchEvent,来自react源码。这时,我们就可以确定是react这一层出现了性能问题。

嗯,轮到Perf出场了:

上图表示,有1000次不必要的渲染发生在TodoItem组件上.

打开react面板,我们来看看组件的层次和相应的state、props值:

TodoItem是Todos的子组件,当我们在输入框输入字母“s”时,Todos的state值发生改变时,文章开头所说的react的渲染机制导致Todos下的1000个TodoItem组件都会重新渲染一次。但是,TodoItem的展现其实没有任何变化。
从代码中,我们可以看出,TodoItem组件展现只跟props(deleteItem、item)相关。props没有变化,TodoItem就没必要渲染。

所以,我们应该优化下TodoItem的SCU方法:

class TodoItem extends Component {...//在props没有变化的时候返回false,不重新渲染shouldComponentUpdate(nextState,nextProps) {if(this.props.item == nextProps.item && this.props.deleteItem == nextProps.deleteItem){return false;}return true;}render() {... }}

(PS: TodoItem中的SCU方法,使用的是浅比较,也可以使用PureComponent代替。实际项目中,往往需要使用复杂的深比较,可以考虑使用Immutable.js)

验证下优化效果,使用Perf测试,发现1000个多余的渲染被干掉了!
再次使用Timeline分析,Event(keypress)耗时从98.5ms降到了26.49ms,性能提升了2.7倍:

疗效还不错!

第二次优化

通过SCU返回false,我们避免了无谓的渲染。但是,我们还是调用了1000次TodoItem的SCU方法,这也是一笔不小的性能开支。

是否可以不用调用呢?通过合理地规划组件粒度,可以做到:

//将增加待办事项抽象成一个组件
class AddItem extends Component{constructor(props) {super(props);this.state = {text:""};}static PropTypes = {addTask:PropTypes.func.isRequired};addTask = (e)=>{e.preventDefault();this.props.addTask(this.state.text);};render(){return (<form onSubmit={this.addTask}><input value={this.state.text} onChange={(v)=>{this.setState({text:v.target.value});}}/><button>添加</button></form>);}
}class Todos extends Component{constructor(props) {super(props);this.state = {items: this.props.initialItems,};}static propTypes = {initialItems: PropTypes.arrayOf(PropTypes.shape({text: PropTypes.string.isRequired,id: PropTypes.number.isRequired,}).isRequired).isRequired,};addTask = (text)=>{this.setState({items: [{id: ID++, text:text}].concat(this.state.items),text: '',});};deleteItem = (itemId)=>{this.setState({items: this.state.items.filter((item) => item.id !== itemId),});};render() {return (<div><h1>待办事项V3</h1><AddItem addTask={this.addTask}/>{this.state.items.map((item) => {return (<TodoItem key={item.id}item={item}deleteItem={this.deleteItem}/>);})}</div>);}
}

把增加待办事项抽象成一个AddItem组件。这样一来,组件树从原来的

变成

输入信息时触发变化的text这个state值,被下放到AddItem组件来管理,因此不会导致兄弟组件(TodoItem)的重新渲染。

再次运行Timeline测试,这时Event(keypress)耗时从26.49ms降到了7.98ms,性能提升了2.3倍:

至此,性能优化完毕~

React进阶—性能优化相关推荐

  1. [react] react的性能优化在哪个生命周期?它优化的原理是什么?

    [react] react的性能优化在哪个生命周期?它优化的原理是什么? shouldComponentUpdate 减少不必要的重新渲染 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易 ...

  2. JavaScript 大揭秘:React、性能优化以及多线程

    @开发者,在后端仅提供原始数据集的情况下,如何让所有搜索逻辑都在前端完成?不仅如此,还要能够实现预测用户输入.忽略错别字输入.快速自动补齐等智能功能?本文的作者就深入 JavaScript 技术特性, ...

  3. 如何对react进行性能优化

    React本身就非常关注性能,其提供的虚拟DOM搭配上DIff算法,实现对DOM操作最小粒度的改变也是非常高效的,然而其组件的渲染机制,也决定了在对组件更新时还可以进行更细致的优化.  react组件 ...

  4. Android 进阶——性能优化之电量优化全攻略及实战小结(二)

    文章大纲 引言 一.在低电耗模式和应用待机模式下进行测试 1.在低电耗模式下测试您的应用 2.在应用待机模式下测试您的应用 3.列入白名单的可接受用例 4.确定当前充电状态 5.监控充电状态变化 6. ...

  5. 前端jquery、vue、react之性能优化

    一.jquery 选择器性能优化建议 总是从#id选择器来继承 这是jQuery选择器的一条黄金法则.jQuery选择一个元素最快的方法就是用ID来选择了. $( '#content' ).hide( ...

  6. Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)

    文章大纲 引言 一.内存泄漏和内存溢出概述 二.Java运行时内存模型 1.线程私有数据区 1.1.程序计数器PC 1.2.虚拟机栈 1.3 本地方法栈 2.所有线程共享数据区 2.1.Java堆 2 ...

  7. React 的性能优化(一)当 PureComponent 遇上 ImmutableJS

    一.痛点 在我们的印象中,React 好像就意味着组件化.高性能,我们永远只需要关心数据整体,两次数据之间的 UI 如何变化,则完全交给 React Virtual Dom 的 Diff 算法 去做. ...

  8. React 组件性能优化之 PureComponent 的使用

    文章出自个人博客:https://knightyun.github.io/2021/05/09/js-react-purecomponent,转载请申明 在 React 类组件中,如果状态(state ...

  9. React Native性能优化总结

    React Native开源已经接近2年时间,京东.携程.58同城等互联网公司都在使用,公司于今年也开始使用,并推广到各个新项目.本文重点分享我们遇到的一些问题以及优化方案. 一.为什么会引入Reac ...

最新文章

  1. 如何实现 集群化/Session 复制-doc(cluster-howto.html)
  2. Nginx if 条件判断
  3. LOOPS HDU - 3853 (概率dp):(希望通过该文章梳理自己的式子推导)
  4. 未越狱设备提取数据_从三星设备中提取健康数据
  5. mysql5.7主从复制遇到的坑
  6. css文件插入背景音乐,h5页面添加背景音乐
  7. easypoi 导入失败返回错误文件_从Excel批量导入数据说到ForkJoin的原理
  8. 客户端连接故障检查流程手段
  9. ImageButton 按钮查看商品详细信息
  10. qt mdi 子窗口关闭再打开_QT 信号的使用方法
  11. 斐讯k3 搭建php环境,斐讯K3刷机教程官改V2.1D或者其它版本教程
  12. idea激活到2100年
  13. 深入了解示波器(三):示波器的带宽
  14. 五笔速成法--教你5小时学会五笔打字
  15. oracle 修改lsnrctl,lsnrctl oracle 监听器 命令行
  16. 【Linux操作系统基础】第六章 Linux中的进程管理
  17. 005_redis_set集合
  18. 爱丁堡计算机专业硕士世界排名,爱丁堡大学计算机世界排名
  19. 基于python下django框架 实现多用户商城平台详细设计
  20. linux 导出数据库数据或者表结构

热门文章

  1. 17秋 软件工程 团队第五次作业 Alpha Scrum3
  2. SpringMVC—对Ajax的处理(含 JSON 类型)(2)
  3. [转]wireshark 实用过滤表达式(针对ip、协议、端口、长度和内容) 实例介绍
  4. M| SQL 导入导出的时候数据库表的主键和自动编号丢失 怎么办
  5. 『程序员』 [程序人生]程序员几种不同的境界
  6. DELPHI学习---结构类型
  7. 如何在webservice中取得sesssionid
  8. 使用Null Object设计模式[转]
  9. 数据可视化(BI报表的开发)第一天
  10. [TJOI2010]阅读理解