React文档(十八)最佳性能
在内部,React使用好几种聪明的技巧去最小化更新UI所需要的DOM操作。对于很多应用来说,使用React会使得构建用户界面非常之快而且不需要做太多专门的性能优化。虽然如此,还是有一些方法可以让你为React应用加速。
使用生产构建
如果你正在性能测试或者在你的应用里遇到性能测试问题,确保你测试时使用了压缩了的生产构建:
- 对于创建React应用,你需要运行npm run build然后遵循指令
- 对于单文件构建,我们提供生产环境.min.js的文件版本
- 对于模块管理,你需要设置NPDE_ENV=production
- 对于webpack,你需要添加这个到配置文件的plugins里
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin()
- 对于汇总,你需要在commonjs插件之前使用replace插件因此只在开发环境使用的模块就不会被导入。完整的设置例子请看这里。
plugins: [
require('rollup-plugin-replace')({
'process.env.NODE_ENV': JSON.stringify('production')
}),
require('rollup-plugin-commonjs')(),
// ...
]
开发的构建包括了额外的警告很有帮助但是由于额外的统计所以会让程序变慢。
使用chrome performance对组件进行性能分析
在开发模式下,你可以通过使用浏览器里的性能工具来显示组件的实例化,更新和销毁。举个例子:

在chrome浏览器里这样做:
- 加载你的应用,使地址url的查询字符串为?react_perf
- 打开chrome开发者工具的performance面板并且按下record
- 然后做一些你想要测试分析的动作。不要录制超过20秒否则chrome可能会挂起
- 停止录制
- React事件将会成组地出现在user timing标签下面
注意那些数字是相对的因此组件在生产环境下会渲染地更快。还有,这样可以帮助你意识到不相关的UI会错误的更新,还有UI更新的深度和频率。
如今的chrome,edge和IE浏览器支持这个特性,但是我们使用的标准user timing API因此我们希望更多的浏览器可以添加对它的支持。
避免重复渲染
React在渲染出的UI内部建立和维护了一个内层的实现方式。这个内部表示包含了从组建里返回的React元素。这个内部表示让React避免了不必要的创建和关联DOM节点,那样会使速度变慢。有时被提到为“虚拟DOM”,但是在React Native里它同样存在。
当一个组件的props或者state改变了,React通过比较新返回的元素和之前渲染的元素来决定是否一个DOM的更新是必要的。当两者不一样的时候,React会更新DOM。
在一些情况下,你的组件通过重写生命周期函数shouldComponentUpdate可以为程序加速,shouldComponentUpdate是在重新渲染的流程开始之前被触发。这个函数默认会返回true,委托React去更新:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
如果你知道在某些情况下你的组件不需要更新,你可以在shouldComponentUpdate里返回false,来跳过整个渲染流程,包括对该组件和之后的内容调用render()方法。
shouldComponentUpdate应用
下面是组件的树状目录。对于每一个节点,SCU指明了shouldComponentUpdate返回了什么,而vDOMEq指明了是否已经渲染的React元素发生了变化。最终,圆圈的颜色表明了是否组件需要重新渲染。

自从shouldComponentUpdate在树的节点C2处返回了false,React不会试图渲染C2节点,因此在C4和C5节点上也不需要调用shouldComponentUpdate。
对于C1和C3节点,shouldComponentUpdate返回了true,因此React必须往下到叶子节点去检查它们。对于C6shouldComponentUpdate返回了true,自从元素已经发生改变React就必须更新DOM。
最有趣的情况是C8。React必须渲染这个组件,但是自从React元素返回的和之前渲染的一样,那就不必更新DOM。
注意React只是必须改变C6的DOM,这是不可避免的。对于C8,它通过比较跳出了更新,并且对于C2的子树和C7,甚至不需要比较因为shouldComponentUpdate返回了false,所以render就不会被调用。
例子
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和stete.count的值是否有变化。如果它们没有变化,那么组件就不更新。如果你的组件越复杂,你就可以对于props和state使用“表面对比”类似的模式来决定是否组件应该更新。这个模式很常见,React提供了一个帮助工具来实现这个逻辑,它继承自React.PureComponent。所以下面的代码用简单的方式实现了同样的事:
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
大多数情况,可以使用React.PureComponent取代你自己写的shouldComponentUpdate。它只会做一个浅比较,所以当一个props或者state以某种方式突变那么浅比较可能会错过这个变化。
这在复杂的数据结构时就会出现问题。举个例子,这么说吧你想要一个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() {
// This section is bad style and causes a bug
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之间。自从WordAdder类里的handleClick方法里的words数组发生了改变,旧的和新的this.props.words的值会比较为相同的,即使数组中的单词真的发生了变化。ListOfWords因此就不会更新即使它拥有了新的单词。
不会突变的数据的力量
最简单的方法去避免这个问题就是避免去使用可能会突变的props或者state。举个例子,上面的handleClick方法可以使用concat重写:
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
};
你也可以重写改变对象的代码为了避免这个突变,通过类似的方式。举个例子,我们有一个对象名字叫做colormap并且我们想写一个函数来改变colormap.right为'blue'。我们可以这样写:
function updateColorMap(colormap) {
colormap.right = 'blue';
}
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
updateColorMap现在返回一个新对象,而不是改变旧的对象。Object.assign在ES6中并且要求一个polyfill。
这里有一个js建议要添加对象扩展操作符使得不修改而更新对象更加简便:
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
如果你正在创建React App,Object.assign和扩展操作符语法都默认是可用的。
使用不可变的数据结构
- 不可变的:一旦创建,一个合集在其他时间点不能被改变。
- 执着的:新的合集可以通过前一个合集和一个改变来建立。原始的集合在新的集合建立后依然可用。
- 结构分享:新的合集尽可能多的使用和原始集合同样的结构来创建,减少复制到最低限度来提高性能。
不可变性使得追踪改变很简单。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。举个例子,在这段js代码中:
const x = { foo: "bar" };
const y = x;
y.foo = "baz";
x === y; // true
const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false
在这个例子中,自从改变了x一个新的引用返回,我们可以设想x被改变了。
另外两个可以帮助我们使用不可改变的数据的库是seamless-immutable和immutability-helper。
不可变的数据结构提供了方便的方式来追踪对象的变化,这就是我们需要的东西来实现shouldComponentUpdate。这样你就可以获得一个很好的性能提高。
React文档(十八)最佳性能的更多相关文章
- React文档(八)条件渲染
在React中,你可以创建不同的组件各自封装你需要的东西.之后你可以只渲染其中的一部分,这取决于应用的state(状态). 条件渲染在React里就和js里的条件语句一样.使用js里的if或者条件表达 ...
- React文档(十三)思考React
在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...
- React文档(二十四)高阶组件
高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...
- react文档demo实现输入展示搜索结果列表
文档页面地址:https://doc.react-china.org/docs/thinking-in-react.html 该文档只给了具体实现思路,下面是我实现的代码. 初学react,如果有写的 ...
- React文档(一)安装
React是一个灵活的可以用于各种不同项目的框架,你可以用它来写新应用,你也可以逐步将它引进已有的代码库而不用重写整个项目. 试用React 如果你想玩一玩React,那么就去CodePen上试一试. ...
- [译]Selenium Python文档:八、附录:FAQ常见问题
另外一个FAQ:https://github.com/SeleniumHQ/selenium/wiki/Frequently-Asked-Questions 8.1.怎样使用ChromeDriver ...
- Drools文档(八) 规则语言参考
规则语言参考 概述 Drools有一个"本地"的规则语言.这种格式在标点符号上非常轻,并且通过"扩展器"支持自然语言和领域特定的语言,使语言能够变形到您的问题领 ...
- React文档(十六)refs和DOM
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素. 在标准的React数据流中,props是使得父组件和子组件之间交互的唯一方式.你通过props重新 ...
- React文档(二十二)context
React中,通过React组件可以很容易地追踪数据流.当你关注一个组件,你可以发现哪一个props被传递了,这样使得你的应用很容被推断. 在一些情况下,你想要传递数据通过组件树而不需要去手动在每一层 ...
随机推荐
- html5 css练习 定位布局
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- 关于 mysql2 -v '0.3.21'(CentOS7.3)
个人由于没有安装mysql而是装的MariaDB,所以网上说安装mysql,故没有采用,经查阅资料后,详细情况如下: Gem时报错: [root@localhost ~]# gem install m ...
- Vfox数据库导出EXCEL,含有备注型子段
1. 选择菜单“数据”-> “自其他来源”->“来自 Microsoft Query ”. 2. 在出来的“选择数据源” 里面双击第一个选项“<新数据源>”会出来一个“创建新数 ...
- SlidingMenu第三篇 --- SlidingMenu使用介绍
在Activity中通过SlidingMenu的构造方法,直接设置侧滑菜单 public class Main2Activity extends Activity { @Override protec ...
- SpringBoot Controller接收参数的几种常用方式
第一类:请求路径参数1.@PathVariable获取路径参数.即url/{id}这种形式. 2.@RequestParam获取查询参数.即url?name=这种形式 例子GET http://loc ...
- 搭建一个简单的Eureka程序
Eureka集群主要有三个部分Eureka服务器,服务提供者,服务调用者 简单的来说就是服务提供者将服务注册到Eureka服务器,服务调用者对其服务进行查找调用. Eureka服务程序的搭建可参考官方 ...
- ubuntu 下安装 navicat 12
一.去官网下载navicat112_premium_cs_x64 for linux版本二.用tar解压安装包三.navicat解压即可用,直接进入解压后的目录,然后用‘./’运行start_navi ...
- Java运行环境
Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境. Windows 上安装开发环境 Linux 上安装开发环境 安装 Eclipse 运行 Java Cloud Studio ...
- 系统管理--配置Gitlab
很多教程都有配这个,但这个又不能用于”源码管理”模块拉取代码,我一直很困惑这个配置有什么用,然后就找到了该插件的github项目地址才弄明白,链接:https://github.com/jenkins ...
- appium+夜神+python3 环境配置
先感慨一下真的心累啊,踩了好多坑,断断续续四天终于把环境彻底搭建完了,由于之前看的网上的帖子都不是很全而且还带坑,决定自己写一篇综述,作为笔记. 一,首先是安装需要的环境: 需要用到的软件: 1. j ...