浅谈react性能优化的方法


Posted in Javascript onSeptember 05, 2018

React性能优化思路

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

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

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

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

初识react只是为了尽快完成项目,后期进行代码审查时候发现有很多地方需要优化,因此做了个小结。

  • Code Splitting
  • shouldComponentUpdate避免重复渲染
  • 使用不可突变数据结构
  • 组件尽可能的进行拆分、解耦
  • 列表类组件优化
  • bind函数优化
  • 不要滥用props
  • ReactDOMServer进行服务端渲染组件

Code Splitting

Code Splitting 可以帮你“懒加载”代码,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个 bundle 拆分成单个 bundle + 多份动态代码的形式。

webpack提供三种代码分离方法,详情见webpack官网

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 SplitChunks 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

在此,主要了解一下第三种动态导入的方法。

1、例如可以把下面的import方式

import { add } from './math';
console.log(add(16, 26));

改写成动态 import 的形式,让首次加载时不去加载 math 模块,从而减少首次加载资源的体积。

import("./math").then(math => {
 console.log(math.add(16, 26));
});

2、例如引用react的高阶组件react-loadable进行动态import。

import Loadable from 'react-loadable';
import Loading from './loading-component';

const LoadableComponent = Loadable({
 loader: () => import('./my-component'),
 loading: Loading,
});

export default class App extends React.Component {
 render() {
  return <LoadableComponent/>;
 }
}

上面的代码在首次加载时,会先展示一个 loading-component,然后动态加载 my-component 的代码,组件代码加载完毕之后,便会替换掉 loading-component

shouldComponentUpdate避免重复渲染

当一个组件的props或者state改变时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际的DOM。当他们不相等时,React会更新DOM。

在一些情况下,你的组件可以通过重写这个生命周期函数shouldComponentUpdate来提升速度, 它是在重新渲染过程开始前触发的。 这个函数默认返回true,可使React执行更新。

为了进一步说明问题,引用官网的图解释一下,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

浅谈react性能优化的方法

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

  • C1根节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C2节点,红色SCU,表示不需要更新,同时
  • C4、C5作为其子节点也不需要检查更新。
  • C3节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C6节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C7节点,红色SCU,表示不需要更新。
  • C8节点,绿色SCU,表示React需要渲染这个组件;绿色vDOMEq,表示虚拟DOM一致,不更新DOM。

因此,我们可以通过根据自己的业务特性,重载shouldComponentUpdate,只在确认真实DOM需要改变时,再返回true。一般的做法是比较组件的props和state是否真的发生变化,如果发生变化则返回true,否则返回false。引用官网的案例。

class CounterButton extends React.Component {
 constructor(props) {
  super(props);
  this.state = {count: 1};
 }

 shouldComponentUpdate(nextProps, nextState) {
  if (this.props.color !== nextProps.color) {
   return true;
  }
  if (this.state.count !== nextState.count) {
   return true;
  }
  return false;
 }

 render() {
  return (
   <button
    color={this.props.color}
    onClick={() => this.setState(state => ({count: state.count + 1}))}>
    Count: {this.state.count}
   </button>
  );
 }
}

在以上代码中,shouldComponentUpdate只检查props.color和state.count的变化。如果这些值没有变化,组件就不会更新。当你的组件变得更加复杂时,你可以使用类似的模式来做一个“浅比较”,用来比较属性和值以判定是否需要更新组件。这种模式十分常见,因此React提供了一个辅助对象来实现这个逻辑 - 继承自React.PureComponent。

大部分情况下,你可以使用React.PureComponent而不必写你自己的shouldComponentUpdate,它只做一个浅比较。但是当你比较的目标为引用类型数据,浅比较会忽略属性或状态突变的情况,此时你不能使用它,此时你需要关注下面的不可突变数据。

附:数据突变(mutated)是指变量的引用没有改变(指针地址未改变),但是引用指向的数据发生了变化(指针指向的数据发生变更)。例如const x = {foo:'foo'}。x.foo='none' 就是一个突变。

使用不可突变数据结构

引用官网中的例子解释一下突变数据产生的问题。例如,假设你想要一个ListOfWords组件来渲染一个逗号分隔的单词列表,并使用一个带了点击按钮名字叫WordAdder的父组件来给子列表添加一个单词。以下代码并不正确:

class ListOfWords extends React.PureComponent {
 render() {
  return <div>{this.props.words.join(',')}</div>;
 }
}

class WordAdder extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   words: ['marklar']
  };
  this.handleClick = this.handleClick.bind(this);
 }

 handleClick() {
  // 这段内容将会导致代码不会按照你预期的结果运行
  const words = this.state.words;
  words.push('marklar');
  this.setState({words: words});
 }

 render() {
  return (
   <div>
    <button onClick={this.handleClick} />
    <ListOfWords words={this.state.words} />
   </div>
  );
 }
}

导致代码无法正常工作的原因是 PureComponent 仅仅对 this.props.words的新旧值进行“浅比较”。在words值在handleClick中被修改之后,即使有新的单词被添加到数组中,但是this.props.words的新旧值在进行比较时是一样的(引用对象比较),因此 ListOfWords 一直不会发生渲染。
避免此类问题最简单的方式是避免使用值可能会突变的属性或状态,如:

1、数组使用concat,对象使用Object.assign()

handleClick() {
 this.setState(prevState => ({
  words: prevState.words.concat(['marklar'])
 }));
}
// 假设我们有一个叫colormap的对象,下面方法不污染原始对象
function updateColorMap(colormap) {
 return Object.assign({}, colormap, {right: 'blue'});
}

2、ES6支持数组或对象的spread语法

handleClick() {
 this.setState(prevState => ({
  words: [...prevState.words, 'marklar'],
 }));
};
function updateColorMap(colormap) {
 return {...colormap, right: 'blue'};
}

3、使用不可突变数据immutable.js

immutable.js使得变化跟踪很方便。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false

在这个例子中,x突变后返回了一个新的索引,因此我们可以安全的确认x被改变了。

不可突变的数据结构帮助我们轻松的追踪对象变化,从而可以快速的实现shouldComponentUpdate。

具体如何使用可参考下面文章:Immutable 详解及 React 中实践

组件尽可能的进行拆分、解耦

组件尽可能的细分,比如一个input+list组件,可以将list分成一个PureComponent,只在list数据变化时更新。否则在input值变化页面重新渲染的时候,list也需要进行不必要的DOM diff。

列表类组件优化

key属性在组件类之外提供了另一种方式的组件标识。通过key标识,在组件发生增删改、排序等操作时,可以根据key值的位置直接调整DOM顺序,告诉React 避免不必要的渲染而避免性能的浪费。
例,对于一个基于排序的组件渲染:

var items = sortBy(this.state.sortingAlgorithm, this.props.items);
return items.map(function(item){
 return <img src={item.src} />
});

当顺序发生改变时,React 会对元素进行diff操作,并改img的src属性。显示,这样的操作效率是非常低的。这时,我们可以为组件添加一个key属性以唯一的标识组件:

return <img src={item.src} key={item.id} />

增加key后,React就不是diff,而是直接使用insertBefore操作移动组件位置,而这个操作是移动DOM节点最高效的办法。

bind函数

绑定this的方式:一般有下面3种方式:

1、constructor绑定

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this); //构造函数中绑定
}
//然后可以
<p onClick={this.handleClick}>

2、使用时绑定

<p onClick={this.handleClick.bind(this)}>

3、使用箭头函数

<Test click={() => { this.handleClick() }}/>

以上三种方法,第一种最优。

因为第一种构造函数只在组件初始化的时候执行一次,

第二种组件每次render都会执行

第三种在每一次render时候都会生成新的箭头函数。例:Test组件的click属性是个箭头函数,组件重新渲染的时候Test组件就会

因为这个新生成的箭头函数而进行更新,从而产生Test组件的不必要渲染。

不要滥用props

props尽量只传需要的数据,避免多余的更新,尽量避免使用{...props}

ReactDOMServer进行服务端渲染组件

为了用户会更快速地看到完整渲染的页面,可以采用服务端渲染技术,在此了解一下ReactDOMServer。

为了实现SSR,你可能会用nodejs框架(Express、Hapi、Koa)来启动一个web服务器,接着调用 renderToString 方法去渲染你的根组件成为字符串,最后你再输出到 response。

// using Express
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";
app.get("/", (req, res) => {
 res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
 res.write("<div id='content'>"); 
 res.write(renderToString(<MyPage/>));
 res.write("</div></body></html>");
 res.end();
});

客户端使用render方法来生成HTML

import ReactDOM from 'react-dom';
import MyPage from "./MyPage";
ReactDOM.render(<MyPage />, document.getElementById('app'));

react性能检测工具

react16版本之前,我们可以使用react-addons-perf工具来查看,而在最新的16版本,我们只需要在url后加上?react_pref。
首先来了解一下react-addons-perf。

react-addons-perf这是 React 官方推出的一个性能工具包,可以打印出组件渲染的时间、次数、浪费时间等。
简单说几个api,具体用法可参考官网:

  • Perf.start() 开始记录
  • Perf.stop() 结束记录
  • Perf.printInclusive() 查看所有设计到的组件render
  • Perf.printWasted() 查看不需要的浪费组件render

再来了解一下,react16版本的方法,在url后加上?react_pref,就可以在chrome浏览器的performance,我们可以查看User Timeing来查看组件的加载时间。点击record开始记录,注意记录时长不要超过20s,否则可能导致chrome挂起。

浅谈react性能优化的方法

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Mozilla中显示textarea中选择的文字
Sep 07 Javascript
js 页面输出值
Nov 30 Javascript
JS判断移动端访问设备并加载对应CSS样式
Jun 13 Javascript
jquery实现焦点图片随机切换效果的方法
Mar 12 Javascript
node模块机制与异步处理详解
Mar 13 Javascript
JavaScript几种数组去掉重复值的方法推荐
Apr 12 Javascript
深入理解vue-router之keep-alive
Aug 31 Javascript
获取本机IP地址的实例(JavaScript / Node.js)
Nov 24 Javascript
微信小程序适配iphoneX的实现方法
Sep 18 Javascript
Node批量爬取头条视频并保存方法
Sep 20 Javascript
javascript自定义右键菜单插件
Dec 16 Javascript
结合axios对项目中的api请求进行封装操作
Sep 21 Javascript
angularjs性能优化的方法
Sep 05 #Javascript
jQuery实现获取及设置CSS样式操作详解
Sep 05 #jQuery
在vue中给列表中的奇数行添加class的实现方法
Sep 05 #Javascript
Vue.js 实现数据展示全部和收起功能
Sep 05 #Javascript
浅谈vue 单文件探索
Sep 05 #Javascript
快速解决vue动态绑定多个class的官方实例语法无效的问题
Sep 05 #Javascript
jQuery扩展方法实现Form表单与Json互相转换的实例代码
Sep 05 #jQuery
You might like
php表单转换textarea换行符的方法
2010/09/10 PHP
一个好用的PHP验证码类实例分享
2013/12/27 PHP
非常重要的php正则表达式详解
2016/01/04 PHP
win7安装php框架Yii的方法
2016/01/25 PHP
php pdo连接数据库操作示例
2019/11/18 PHP
javascript CSS画图之基础篇
2009/07/29 Javascript
JavaScript 入门基础知识 想学习js的朋友可以参考下
2009/12/26 Javascript
js实现目录定位正文示例
2013/11/14 Javascript
JavaScript返回0-1之间随机数的方法
2015/04/06 Javascript
JavaScript中函数表达式和函数声明及函数声明与函数表达式的不同
2015/11/15 Javascript
JS中Json数据的处理和解析JSON数据的方法详解
2016/06/29 Javascript
javascript创建含数字字母的随机字符串方法总结
2016/08/01 Javascript
JavaScript纯色二维码变成彩色二维码
2020/07/23 Javascript
利用node.js+mongodb如何搭建一个简单登录注册的功能详解
2017/07/30 Javascript
浅谈vue路径优化之resolve
2017/10/13 Javascript
Vue实现搜索 和新闻列表功能简单范例
2018/03/16 Javascript
vue2.0 使用element-ui里的upload组件实现图片预览效果方法
2018/09/04 Javascript
解决Vue 刷新页面导航显示高亮位置不对问题
2019/12/25 Javascript
[40:05]DOTA2上海特级锦标赛A组小组赛#1 EHOME VS MVP.Phx第一局
2016/02/25 DOTA
[02:38]2018DOTA2亚洲邀请赛赛前采访-VGJ.T
2018/04/03 DOTA
Python3一行代码实现图片文字识别的示例
2018/01/15 Python
python队列原理及实现方法示例
2019/11/27 Python
Python远程linux执行命令实现
2020/11/11 Python
网站性能延迟加载图像的五种技巧(小结)
2020/08/13 HTML / CSS
HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题
2021/01/19 HTML / CSS
日本著名的服饰鞋帽综合类购物网站:MAGASEEK
2019/01/09 全球购物
改变生活的男士内衣:SAXX Underwear
2019/08/28 全球购物
服装设计专业毕业生推荐信
2013/11/09 职场文书
电焊工工作岗位职责
2014/02/06 职场文书
农业生产宣传标语
2014/10/08 职场文书
2015最新民情日记范文
2015/06/26 职场文书
python使用XPath解析数据爬取起点小说网数据
2021/04/22 Python
Python 阶乘详解
2021/10/05 Python
zabbix自定义监控nginx状态实现过程
2021/11/01 Servers
浅谈克隆 JavaScript
2021/11/02 Javascript
MySQL池化框架学习接池自定义
2022/07/23 MySQL