在内部,React使用好几种聪明的技巧去最小化更新UI所需要的DOM操作。对于很多应用来说,使用React会使得构建用户界面非常之快而且不需要做太多专门的性能优化。虽然如此,还是有一些方法可以让你为React应用加速。

使用生产构建

如果你正在性能测试或者在你的应用里遇到性能测试问题,确保你测试时使用了压缩了的生产构建:

  • 对于创建React应用,你需要运行npm run build然后遵循指令
  • 对于单文件构建,我们提供生产环境.min.js的文件版本
  • 对于模块管理,你需要设置NPDE_ENV=production
  • 对于webpack,你需要添加这个到配置文件的plugins里
  1. new webpack.DefinePlugin({
  2. 'process.env': {
  3. NODE_ENV: JSON.stringify('production')
  4. }
  5. }),
  6. new webpack.optimize.UglifyJsPlugin()
  • 对于汇总,你需要在commonjs插件之前使用replace插件因此只在开发环境使用的模块就不会被导入。完整的设置例子请看这里
  1. plugins: [
  2. require('rollup-plugin-replace')({
  3. 'process.env.NODE_ENV': JSON.stringify('production')
  4. }),
  5. require('rollup-plugin-commonjs')(),
  6. // ...
  7. ]

开发的构建包括了额外的警告很有帮助但是由于额外的统计所以会让程序变慢。

使用chrome performance对组件进行性能分析

在开发模式下,你可以通过使用浏览器里的性能工具来显示组件的实例化,更新和销毁。举个例子:

在chrome浏览器里这样做:

  1. 加载你的应用,使地址url的查询字符串为?react_perf
  2. 打开chrome开发者工具的performance面板并且按下record
  3. 然后做一些你想要测试分析的动作。不要录制超过20秒否则chrome可能会挂起
  4. 停止录制
  5. 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去更新:

  1. shouldComponentUpdate(nextProps, nextState) {
  2. return true;
  3. }

如果你知道在某些情况下你的组件不需要更新,你可以在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就不会被调用。

例子

如果想让组件只在props.color或者state.count的值变化时重新渲染,你可以像下面这样设定shouldComponentUpdate:
  1. class CounterButton extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {count: 1};
  5. }
  6.  
  7. shouldComponentUpdate(nextProps, nextState) {
  8. if (this.props.color !== nextProps.color) {
  9. return true;
  10. }
  11. if (this.state.count !== nextState.count) {
  12. return true;
  13. }
  14. return false;
  15. }
  16.  
  17. render() {
  18. return (
  19. <button
  20. color={this.props.color}
  21. onClick={() => this.setState(state => ({count: state.count + 1}))}>
  22. Count: {this.state.count}
  23. </button>
  24. );
  25. }
  26. }

在这段代码里,shouldComponentUpdate检查了props.color和stete.count的值是否有变化。如果它们没有变化,那么组件就不更新。如果你的组件越复杂,你就可以对于props和state使用“表面对比”类似的模式来决定是否组件应该更新。这个模式很常见,React提供了一个帮助工具来实现这个逻辑,它继承自React.PureComponent。所以下面的代码用简单的方式实现了同样的事:

  1. class CounterButton extends React.PureComponent {
  2. constructor(props) {
  3. super(props);
  4. this.state = {count: 1};
  5. }
  6.  
  7. render() {
  8. return (
  9. <button
  10. color={this.props.color}
  11. onClick={() => this.setState(state => ({count: state.count + 1}))}>
  12. Count: {this.state.count}
  13. </button>
  14. );
  15. }
  16. }

大多数情况,可以使用React.PureComponent取代你自己写的shouldComponentUpdate。它只会做一个浅比较,所以当一个props或者state以某种方式突变那么浅比较可能会错过这个变化。

这在复杂的数据结构时就会出现问题。举个例子,这么说吧你想要一个ListOfWords组件去渲染一个逗号隔开的单词表,它会有一个WordAdder父组件让你按一下按钮就在列表添加一个单词。下面的代码运行会出错:

  1. class ListOfWords extends React.PureComponent {
  2. render() {
  3. return <div>{this.props.words.join(',')}</div>;
  4. }
  5. }
  6.  
  7. class WordAdder extends React.Component {
  8. constructor(props) {
  9. super(props);
  10. this.state = {
  11. words: ['marklar']
  12. };
  13. this.handleClick = this.handleClick.bind(this);
  14. }
  15.  
  16. handleClick() {
  17. // This section is bad style and causes a bug
  18. const words = this.state.words;
  19. words.push('marklar');
  20. this.setState({words: words});
  21. }
  22.  
  23. render() {
  24. return (
  25. <div>
  26. <button onClick={this.handleClick} />
  27. <ListOfWords words={this.state.words} />
  28. </div>
  29. );
  30. }
  31. }

问题就在于PureComponent会做一个简单的比较在新的和旧的this.props.words之间。自从WordAdder类里的handleClick方法里的words数组发生了改变,旧的和新的this.props.words的值会比较为相同的,即使数组中的单词真的发生了变化。ListOfWords因此就不会更新即使它拥有了新的单词。

不会突变的数据的力量

最简单的方法去避免这个问题就是避免去使用可能会突变的props或者state。举个例子,上面的handleClick方法可以使用concat重写:

  1. handleClick() {
  2. this.setState(prevState => ({
  3. words: prevState.words.concat(['marklar'])
  4. }));
  5. }
ES6支持一种对于数组的扩展操作符可以让这里更简单。如果你是创建React App,那么这个语法默认是可用的。
  1. handleClick() {
  2. this.setState(prevState => ({
  3. words: [...prevState.words, 'marklar'],
  4. }));
  5. };

你也可以重写改变对象的代码为了避免这个突变,通过类似的方式。举个例子,我们有一个对象名字叫做colormap并且我们想写一个函数来改变colormap.right为'blue'。我们可以这样写:

  1. function updateColorMap(colormap) {
  2. colormap.right = 'blue';
  3. }
不需改变原来的对象,我们可以使用Object.assign方法:
  1. function updateColorMap(colormap) {
  2. return Object.assign({}, colormap, {right: 'blue'});
  3. }

updateColorMap现在返回一个新对象,而不是改变旧的对象。Object.assign在ES6中并且要求一个polyfill。

这里有一个js建议要添加对象扩展操作符使得不修改而更新对象更加简便:

  1. function updateColorMap(colormap) {
  2. return {...colormap, right: 'blue'};
  3. }

如果你正在创建React App,Object.assign和扩展操作符语法都默认是可用的。

使用不可变的数据结构

Immutable.js是解决这个问题的另一种方法。它通过结构共享提供不可突变的,持久的集合:
  • 不可变的:一旦创建,一个合集在其他时间点不能被改变。
  • 执着的:新的合集可以通过前一个合集和一个改变来建立。原始的集合在新的集合建立后依然可用。
  • 结构分享:新的合集尽可能多的使用和原始集合同样的结构来创建,减少复制到最低限度来提高性能。

不可变性使得追踪改变很简单。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。举个例子,在这段js代码中:

  1. const x = { foo: "bar" };
  2. const y = x;
  3. y.foo = "baz";
  4. x === y; // true
虽然y被编辑了,自从它和x引用的是同一个对象,这个比较返回了true。你可以使用immutable.js来写相似的代码:
  1. const SomeRecord = Immutable.Record({ foo: null });
  2. const x = new SomeRecord({ foo: 'bar' });
  3. const y = x.set('foo', 'baz');
  4. x === y; // false

在这个例子中,自从改变了x一个新的引用返回,我们可以设想x被改变了。

另外两个可以帮助我们使用不可改变的数据的库是seamless-immutable和immutability-helper。

不可变的数据结构提供了方便的方式来追踪对象的变化,这就是我们需要的东西来实现shouldComponentUpdate。这样你就可以获得一个很好的性能提高。

React文档(十八)最佳性能的更多相关文章

  1. React文档(八)条件渲染

    在React中,你可以创建不同的组件各自封装你需要的东西.之后你可以只渲染其中的一部分,这取决于应用的state(状态). 条件渲染在React里就和js里的条件语句一样.使用js里的if或者条件表达 ...

  2. React文档(十三)思考React

    在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...

  3. React文档(二十四)高阶组件

    高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...

  4. react文档demo实现输入展示搜索结果列表

    文档页面地址:https://doc.react-china.org/docs/thinking-in-react.html 该文档只给了具体实现思路,下面是我实现的代码. 初学react,如果有写的 ...

  5. React文档(一)安装

    React是一个灵活的可以用于各种不同项目的框架,你可以用它来写新应用,你也可以逐步将它引进已有的代码库而不用重写整个项目. 试用React 如果你想玩一玩React,那么就去CodePen上试一试. ...

  6. [译]Selenium Python文档:八、附录:FAQ常见问题

    另外一个FAQ:https://github.com/SeleniumHQ/selenium/wiki/Frequently-Asked-Questions 8.1.怎样使用ChromeDriver ...

  7. Drools文档(八) 规则语言参考

    规则语言参考 概述 Drools有一个"本地"的规则语言.这种格式在标点符号上非常轻,并且通过"扩展器"支持自然语言和领域特定的语言,使语言能够变形到您的问题领 ...

  8. React文档(十六)refs和DOM

    Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素. 在标准的React数据流中,props是使得父组件和子组件之间交互的唯一方式.你通过props重新 ...

  9. React文档(二十二)context

    React中,通过React组件可以很容易地追踪数据流.当你关注一个组件,你可以发现哪一个props被传递了,这样使得你的应用很容被推断. 在一些情况下,你想要传递数据通过组件树而不需要去手动在每一层 ...

随机推荐

  1. CentOS 7 安装samba服务

    STEP 1. 安装 #安装 [root@study ~]yum install smaba [root@study ~]systemctl start smb nmb STEP 2. 建立共享目录以 ...

  2. 判断文件的编码 python

    import chardet import string path1= r'C:\Users\25456\Desktop' path = path1 + r'\深度学习.txt' with open( ...

  3. Python文件操作中的方法:.write()换行

    active =Truewhile active: message =input("\nPlease input your name:\n") if message =='q': ...

  4. C#中的as关键字

    你能进到这篇文章…… 首先你可能明白类似于 double num=0;int m=(int) num; 或者你明白 int m=209;String str=m.toString();或者说你甚至见过 ...

  5. 语音活性检测器py-webrtcvad安装使用

    谷歌为WebRTC项目开发的VAD是目前最优秀.最先进和免费的产品之一.webrtcvad是WebRTC语音活动检测器(VAD)的python接口.兼容python2和python3.功能是将一段音频 ...

  6. python交互的几种方式

    # 第一种交互方式 name = input("name:")age = input("age:")job = input("job:")s ...

  7. [最新]ABP ASP.NET Zero v5.5.2 破解

    参考https://www.cnblogs.com/xajh/p/8428818.html后, 搞定了Abp.AspNetZeroCore.dll.Abp.AspNetZeroCore.Web.dll ...

  8. 如何在Linux 中获取硬盘分区或文件系统的UUID?

    作为一个 Linux 系统管理员,你应该知道如何去查看分区的 UUID 或文件系统的 UUID.因为现在大多数的 Linux 系统都使用 UUID 挂载分区.你可以在 /etc/fstab 文件中可以 ...

  9. CentOS与Win7远程桌面互通

    在CentOS上装上Rdesktop即可连接Windows,如下命令,第一次执行时报错,提示CredSSP required by server. [root@localhost ~]# rdeskt ...

  10. tomcat启动命令行中文乱码

    1.找到${CATALINA_HOME}/conf/logging.properties 2.添加语句:java.util.logging.ConsoleHandler.encoding = GBK ...